mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 10:12:34 -05:00
Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
This commit is contained in:
@@ -7,7 +7,7 @@ import 'vs/css!./media/tree';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
@@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
|
||||
class TreeElementsDragAndDropData<T, TFilterData, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||
|
||||
set context(context: TContext | undefined) {
|
||||
this.data.context = context;
|
||||
}
|
||||
|
||||
get context(): TContext | undefined {
|
||||
return this.data.context;
|
||||
}
|
||||
|
||||
constructor(private data: ElementsDragAndDropData<ITreeNode<T, TFilterData>, TContext>) {
|
||||
super(data.elements.map(node => node.element));
|
||||
}
|
||||
}
|
||||
|
||||
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
|
||||
if (data instanceof ElementsDragAndDropData) {
|
||||
const nodes = (data as ElementsDragAndDropData<ITreeNode<T, TFilterData>>).elements;
|
||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
||||
return new TreeElementsDragAndDropData(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
return this.dnd.getDragURI(node.element);
|
||||
}
|
||||
|
||||
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
|
||||
getDragLabel(nodes: ITreeNode<T, TFilterData>[], originalEvent: DragEvent): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element));
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
}, 500);
|
||||
}
|
||||
|
||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
|
||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
|
||||
if (!raw) {
|
||||
const accept = typeof result === 'boolean' ? result : result.accept;
|
||||
const effect = typeof result === 'boolean' ? undefined : result.effect;
|
||||
@@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
|
||||
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
onDragEnd(originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(originalEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
|
||||
@@ -141,12 +161,16 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
|
||||
}
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
...options.accessibilityProvider,
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element);
|
||||
},
|
||||
getAriaLevel(node) {
|
||||
return node.depth;
|
||||
}
|
||||
},
|
||||
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
|
||||
return options.accessibilityProvider!.getActiveDescendantId!(node.element);
|
||||
})
|
||||
},
|
||||
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
|
||||
...options.keyboardNavigationLabelProvider,
|
||||
@@ -211,6 +235,8 @@ export enum RenderIndentGuides {
|
||||
interface ITreeRendererOptions {
|
||||
readonly indent?: number;
|
||||
readonly renderIndentGuides?: RenderIndentGuides;
|
||||
// TODO@joao replace this with collapsible: boolean | 'ondemand'
|
||||
readonly hideTwistiesOfChildlessElements?: boolean;
|
||||
}
|
||||
|
||||
interface IRenderData<TTemplateData> {
|
||||
@@ -244,6 +270,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
private renderedElements = new Map<T, ITreeNode<T, TFilterData>>();
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, IRenderData<TTemplateData>>();
|
||||
private indent: number = TreeRenderer.DefaultIndent;
|
||||
private hideTwistiesOfChildlessElements: boolean = false;
|
||||
|
||||
private shouldRenderIndentGuides: boolean = false;
|
||||
private renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>();
|
||||
@@ -290,6 +317,10 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') {
|
||||
this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements;
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
|
||||
@@ -309,7 +340,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
}
|
||||
|
||||
const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
|
||||
templateData.twistie.style.marginLeft = `${indent}px`;
|
||||
templateData.twistie.style.paddingLeft = `${indent}px`;
|
||||
templateData.indent.style.width = `${indent + this.indent - 16}px`;
|
||||
|
||||
this.renderTwistie(node, templateData);
|
||||
@@ -365,10 +396,12 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
this.renderer.renderTwistie(node.element, templateData.twistie);
|
||||
}
|
||||
|
||||
toggleClass(templateData.twistie, 'codicon', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'collapsible', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed);
|
||||
if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
|
||||
addClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible');
|
||||
toggleClass(templateData.twistie, 'collapsed', node.collapsed);
|
||||
} else {
|
||||
removeClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible', 'collapsed');
|
||||
}
|
||||
|
||||
if (node.collapsible) {
|
||||
templateData.container.setAttribute('aria-expanded', String(!node.collapsed));
|
||||
@@ -430,12 +463,16 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
|
||||
nodes.forEach(node => {
|
||||
const ref = model.getNodeLocation(node);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
try {
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
|
||||
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
|
||||
set.add(node);
|
||||
} else if (parentRef) {
|
||||
set.add(model.getNode(parentRef));
|
||||
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
|
||||
set.add(node);
|
||||
} else if (parentRef) {
|
||||
set.add(model.getNode(parentRef));
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
});
|
||||
|
||||
@@ -601,14 +638,14 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
const controls = append(this.domNode, $('.controls'));
|
||||
|
||||
this._filterOnType = !!tree.options.filterOnType;
|
||||
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
|
||||
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter.codicon.codicon-list-selection'));
|
||||
this.filterOnTypeDomNode.type = 'checkbox';
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.filterOnTypeDomNode.tabIndex = -1;
|
||||
this.updateFilterOnTypeTitle();
|
||||
domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables);
|
||||
|
||||
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear'));
|
||||
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear.codicon.codicon-close'));
|
||||
this.clearDomNode.tabIndex = -1;
|
||||
this.clearDomNode.title = localize('clear', "Clear");
|
||||
|
||||
@@ -657,7 +694,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
|
||||
const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
|
||||
.filter(e => e.key !== 'Dead')
|
||||
.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(this.keyboardNavigationEventFilter || (() => true))
|
||||
.filter(() => this.automaticKeyboardNavigation || this.triggered)
|
||||
@@ -918,7 +955,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
|
||||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly dnd?: ITreeDragAndDrop<T>;
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
|
||||
readonly additionalScrollHeight?: number;
|
||||
@@ -1385,8 +1421,13 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
return this.view.renderHeight;
|
||||
}
|
||||
|
||||
get firstVisibleElement(): T {
|
||||
get firstVisibleElement(): T | undefined {
|
||||
const index = this.view.firstVisibleIndex;
|
||||
|
||||
if (index < 0 || index >= this.view.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = this.view.element(index);
|
||||
return node.element;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
@@ -19,6 +19,8 @@ import { toggleClass } from 'vs/base/browser/dom';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
@@ -149,20 +151,24 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
|
||||
};
|
||||
}
|
||||
|
||||
export enum ChildrenResolutionReason {
|
||||
Refresh,
|
||||
Expand
|
||||
}
|
||||
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||
|
||||
export interface IChildrenResolutionEvent<T> {
|
||||
readonly element: T | null;
|
||||
readonly reason: ChildrenResolutionReason;
|
||||
set context(context: TContext | undefined) {
|
||||
this.data.context = context;
|
||||
}
|
||||
|
||||
get context(): TContext | undefined {
|
||||
return this.data.context;
|
||||
}
|
||||
|
||||
constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
|
||||
super(data.elements.map(node => node.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
|
||||
if (data instanceof ElementsDragAndDropData) {
|
||||
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
|
||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
||||
return new AsyncDataTreeElementsDragAndDropData(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -176,9 +182,9 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||
return this.dnd.getDragURI(node.element as T);
|
||||
}
|
||||
|
||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
|
||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -197,6 +203,12 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
||||
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
onDragEnd(originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(originalEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||
@@ -218,9 +230,16 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
}
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
...options.accessibilityProvider,
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
|
||||
return options.accessibilityProvider!.getAriaLevel!(node.element as T);
|
||||
}),
|
||||
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
|
||||
return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
|
||||
})
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(e, parentVisibility) {
|
||||
@@ -271,10 +290,10 @@ function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDa
|
||||
node.children.forEach(child => dfs(child, fn));
|
||||
}
|
||||
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable, IThemable {
|
||||
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
protected readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly sorter?: ITreeSorter<T>;
|
||||
private readonly collapseByDefault?: { (e: T): boolean; };
|
||||
@@ -282,7 +301,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
|
||||
|
||||
private readonly identityProvider?: IIdentityProvider<T>;
|
||||
protected readonly identityProvider?: IIdentityProvider<T>;
|
||||
private readonly autoExpandSingleChildren: boolean;
|
||||
|
||||
private readonly _onDidRender = new Emitter<void>();
|
||||
@@ -323,7 +342,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
protected user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
@@ -411,10 +430,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return this.tree.renderHeight;
|
||||
}
|
||||
|
||||
get firstVisibleElement(): T {
|
||||
return this.tree.firstVisibleElement!.element as T;
|
||||
}
|
||||
|
||||
get lastVisibleElement(): T {
|
||||
return this.tree.lastVisibleElement!.element as T;
|
||||
}
|
||||
@@ -445,7 +460,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
|
||||
|
||||
await this._updateChildren(input, true, viewStateContext);
|
||||
await this._updateChildren(input, true, false, viewStateContext);
|
||||
|
||||
if (viewStateContext) {
|
||||
this.tree.setFocus(viewStateContext.focus);
|
||||
@@ -457,11 +472,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise<void> {
|
||||
await this._updateChildren(element, recursive);
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender);
|
||||
}
|
||||
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
@@ -471,7 +486,17 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
|
||||
const node = this.getDataNode(element);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext);
|
||||
|
||||
if (rerender) {
|
||||
try {
|
||||
this.tree.rerender(node);
|
||||
} catch {
|
||||
// missing nodes are fine, this could've resulted from
|
||||
// parallel refresh calls, removing `node` altogether
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resort(element: TInput | T = this.root.element, recursive = true): void {
|
||||
@@ -653,18 +678,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
await this.refreshNode(node, recursive, viewStateContext);
|
||||
this.render(node, viewStateContext);
|
||||
|
||||
if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
|
||||
const treeNode = this.tree.getNode(node);
|
||||
const visibleChildren = treeNode.children.filter(node => node.visible);
|
||||
|
||||
if (visibleChildren.length === 1) {
|
||||
await this.tree.expand(visibleChildren[0].element, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
@@ -752,12 +768,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
result = createCancelablePromise(async () => {
|
||||
const children = await this.dataSource.getChildren(node.element!);
|
||||
|
||||
if (this.sorter) {
|
||||
children.sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return children;
|
||||
return this.processChildren(children);
|
||||
});
|
||||
|
||||
this.refreshPromises.set(node, result);
|
||||
@@ -770,7 +781,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
if (deep) {
|
||||
this.collapse(node.element.element as T);
|
||||
} else {
|
||||
this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
|
||||
this.refreshAndRenderNode(node.element, false)
|
||||
.catch(onUnexpectedError);
|
||||
}
|
||||
}
|
||||
@@ -783,13 +794,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const nodesToForget = new Map<T, IAsyncDataTreeNode<TInput, T>>();
|
||||
const childrenTreeNodesById = new Map<string, ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>>();
|
||||
const childrenTreeNodesById = new Map<string, { node: IAsyncDataTreeNode<TInput, T>, collapsed: boolean }>();
|
||||
|
||||
for (const child of node.children) {
|
||||
nodesToForget.set(child.element as T, child);
|
||||
|
||||
if (this.identityProvider) {
|
||||
childrenTreeNodesById.set(child.id!, this.tree.getNode(child));
|
||||
const collapsed = this.tree.isCollapsed(child);
|
||||
childrenTreeNodesById.set(child.id!, { node: child, collapsed });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,10 +822,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const id = this.identityProvider.getId(element).toString();
|
||||
const childNode = childrenTreeNodesById.get(id);
|
||||
const result = childrenTreeNodesById.get(id);
|
||||
|
||||
if (childNode) {
|
||||
const asyncDataTreeNode = childNode.element!;
|
||||
if (result) {
|
||||
const asyncDataTreeNode = result.node;
|
||||
|
||||
nodesToForget.delete(asyncDataTreeNode.element as T);
|
||||
this.nodes.delete(asyncDataTreeNode.element as T);
|
||||
@@ -823,8 +835,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
asyncDataTreeNode.hasChildren = hasChildren;
|
||||
|
||||
if (recursive) {
|
||||
if (childNode.collapsed) {
|
||||
dfs(asyncDataTreeNode, node => node.stale = true);
|
||||
if (result.collapsed) {
|
||||
asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
|
||||
asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
|
||||
asyncDataTreeNode.stale = true;
|
||||
} else {
|
||||
childrenToRefresh.push(asyncDataTreeNode);
|
||||
}
|
||||
@@ -866,10 +880,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
node.children.splice(0, node.children.length, ...children);
|
||||
|
||||
// TODO@joao this doesn't take filter into account
|
||||
if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
|
||||
children[0].collapsedByDefault = false;
|
||||
childrenToRefresh.push(children[0]);
|
||||
}
|
||||
|
||||
return childrenToRefresh;
|
||||
}
|
||||
|
||||
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children);
|
||||
|
||||
@@ -881,6 +901,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
if (node.stale) {
|
||||
return {
|
||||
element: node,
|
||||
collapsible: node.hasChildren,
|
||||
collapsed: true
|
||||
};
|
||||
}
|
||||
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
|
||||
@@ -899,6 +927,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
protected processChildren(children: T[]): T[] {
|
||||
if (this.sorter) {
|
||||
children.sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
@@ -994,6 +1030,12 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
|
||||
}
|
||||
}
|
||||
|
||||
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (this.renderer.disposeCompressedElements) {
|
||||
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.templateData);
|
||||
}
|
||||
@@ -1023,12 +1065,19 @@ function asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options?: IComp
|
||||
}
|
||||
|
||||
export interface ICompressibleAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
|
||||
}
|
||||
|
||||
export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
|
||||
|
||||
protected readonly tree!: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
|
||||
private filter?: ITreeFilter<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -1037,9 +1086,10 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
|
||||
private compressionDelegate: ITreeCompressionDelegate<T>,
|
||||
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
|
||||
dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
options: ICompressibleAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(user, container, virtualDelegate, renderers, dataSource, options);
|
||||
this.filter = options.filter;
|
||||
}
|
||||
|
||||
protected createTree(
|
||||
@@ -1062,4 +1112,137 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
|
||||
...super.asTreeElement(node, viewStateContext)
|
||||
};
|
||||
}
|
||||
|
||||
updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
|
||||
this.tree.updateOptions(options);
|
||||
}
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
|
||||
}
|
||||
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
const focus = this.getFocus().map(getId);
|
||||
const selection = this.getSelection().map(getId);
|
||||
|
||||
const expanded: string[] = [];
|
||||
const root = this.tree.getCompressedTreeNode();
|
||||
const queue = [root];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
|
||||
if (node !== root && node.collapsible && !node.collapsed) {
|
||||
for (const asyncNode of node.element!.elements) {
|
||||
expanded.push(getId(asyncNode.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
queue.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, expanded, scrollTop: this.scrollTop };
|
||||
}
|
||||
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
if (!this.identityProvider) {
|
||||
return super.render(node, viewStateContext);
|
||||
}
|
||||
|
||||
// Preserve traits across compressions. Hacky but does the trick.
|
||||
// This is hard to fix properly since it requires rewriting the traits
|
||||
// across trees and lists. Let's just keep it this way for now.
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
const getUncompressedIds = (nodes: IAsyncDataTreeNode<TInput, T>[]): Set<string> => {
|
||||
const result = new Set<string>();
|
||||
|
||||
for (const node of nodes) {
|
||||
const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
|
||||
|
||||
if (!compressedNode.element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const node of compressedNode.element.elements) {
|
||||
result.add(getId(node.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode<TInput, T>[]);
|
||||
const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode<TInput, T>[]);
|
||||
|
||||
super.render(node, viewStateContext);
|
||||
|
||||
const selection = this.getSelection();
|
||||
let didChangeSelection = false;
|
||||
|
||||
const focus = this.getFocus();
|
||||
let didChangeFocus = false;
|
||||
|
||||
const visit = (node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>> | null, TFilterData>) => {
|
||||
const compressedNode = node.element;
|
||||
|
||||
if (compressedNode) {
|
||||
for (let i = 0; i < compressedNode.elements.length; i++) {
|
||||
const id = getId(compressedNode.elements[i].element as T);
|
||||
|
||||
if (oldSelection.has(id)) {
|
||||
selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
|
||||
didChangeSelection = true;
|
||||
}
|
||||
|
||||
if (oldFocus.has(id)) {
|
||||
focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
|
||||
didChangeFocus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach(visit);
|
||||
};
|
||||
|
||||
visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
|
||||
|
||||
if (didChangeSelection) {
|
||||
this.setSelection(selection);
|
||||
}
|
||||
|
||||
if (didChangeFocus) {
|
||||
this.setFocus(focus);
|
||||
}
|
||||
}
|
||||
|
||||
// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
|
||||
// and we have to filter everything beforehand
|
||||
// Related to #85193 and #85835
|
||||
protected processChildren(children: T[]): T[] {
|
||||
if (this.filter) {
|
||||
children = children.filter(e => {
|
||||
const result = this.filter!.filter(e, TreeVisibility.Visible);
|
||||
const visibility = getVisibility(result);
|
||||
|
||||
if (visibility === TreeVisibility.Recurse) {
|
||||
throw new Error('Recursive tree visibility not supported in async data compressed trees');
|
||||
}
|
||||
|
||||
return visibility === TreeVisibility.Visible;
|
||||
});
|
||||
}
|
||||
|
||||
return super.processChildren(children);
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibility<TFilterData>(filterResult: TreeFilterResult<TFilterData>): TreeVisibility {
|
||||
if (typeof filterResult === 'boolean') {
|
||||
return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
} else if (isFilterResult(filterResult)) {
|
||||
return getVisibleState(filterResult.visibility);
|
||||
} else {
|
||||
return getVisibleState(filterResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
|
||||
readonly children?: ISequence<ICompressedTreeElement<T>>;
|
||||
readonly incompressible?: boolean;
|
||||
}
|
||||
|
||||
@@ -100,16 +100,15 @@ export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): IC
|
||||
|
||||
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
|
||||
if (treeElement.element === element) {
|
||||
return { element, children };
|
||||
return { ...treeElement, children };
|
||||
}
|
||||
|
||||
return {
|
||||
...treeElement,
|
||||
children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
|
||||
};
|
||||
return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) };
|
||||
}
|
||||
|
||||
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
@@ -122,7 +121,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
|
||||
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
|
||||
private enabled: boolean = true;
|
||||
private enabled: boolean;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
@@ -132,13 +131,13 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: ISequence<ICompressedTreeElement<T>> | undefined
|
||||
): void {
|
||||
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren);
|
||||
@@ -368,6 +367,7 @@ function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwra
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
return this.model.resort(element, recursive);
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
getCompressedTreeNode(location: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
|
||||
return this.model.getNode(location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
|
||||
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export interface IDataTreeViewState {
|
||||
|
||||
@@ -442,6 +442,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
if (visibility === TreeVisibility.Hidden) {
|
||||
node.visible = false;
|
||||
node.renderNodeCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-panel-view .panel > .panel-header h3.title {
|
||||
.monaco-pane-view .pane > .pane-header h3.title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 18px;
|
||||
left: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
.monaco-tl-twistie {
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
margin-right: 6px;
|
||||
padding-right: 6px;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
display: flex !important;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
@@ -56,7 +56,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
}
|
||||
|
||||
interface ICompressedTreeNodeProvider<T, TFilterData> {
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
getCompressedTreeNode(location: T | null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData>;
|
||||
}
|
||||
|
||||
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
|
||||
@@ -69,7 +69,7 @@ interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
|
||||
readonly data: TTemplateData;
|
||||
}
|
||||
|
||||
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
|
||||
class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
readonly onDidChangeTwistieState: Event<T> | undefined;
|
||||
@@ -93,7 +93,7 @@ class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRender
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
|
||||
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
|
||||
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
|
||||
if (compressedTreeNode.element.elements.length === 1) {
|
||||
templateData.compressedTreeNode = undefined;
|
||||
@@ -132,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider<T> extends IKeyboa
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
|
||||
}
|
||||
@@ -144,7 +145,7 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
|
||||
let compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
|
||||
try {
|
||||
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
|
||||
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
} catch {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
|
||||
}
|
||||
@@ -159,6 +160,10 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> implements ICompressedTreeNodeProvider<T, TFilterData> {
|
||||
|
||||
protected model!: CompressibleObjectTreeModel<T, TFilterData>;
|
||||
@@ -171,7 +176,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
options: ICompressibleObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
const compressedTreeNodeProvider = () => this;
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer<T, TFilterData, any>(compressedTreeNodeProvider, r));
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
|
||||
}
|
||||
|
||||
@@ -183,15 +188,15 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
return new CompressibleObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
return this.model.isCompressionEnabled();
|
||||
updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void {
|
||||
super.updateOptions(optionsUpdate);
|
||||
|
||||
if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
|
||||
this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
setCompressionEnabled(enabled: boolean): void {
|
||||
this.model.setCompressionEnabled(enabled);
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getCompressedTreeNode(element)!;
|
||||
getCompressedTreeNode(element: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
|
||||
return this.model.getCompressedTreeNode(element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
|
||||
}
|
||||
|
||||
const node = this.nodes.get(element)!;
|
||||
const node = this.nodes.get(element);
|
||||
|
||||
if (!node) {
|
||||
throw new TreeError(this.user, `Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
const location = this.model.getNodeLocation(node);
|
||||
const parentLocation = this.model.getParentNodeLocation(location);
|
||||
const parent = this.model.getNode(parentLocation);
|
||||
|
||||
Reference in New Issue
Block a user