Move legacy tree from vs to sql (#20622)
* Move legacy tree from vs to sql * fix tests * exemption * Fix lint
31
src/sql/base/parts/tree/browser/loading-dark.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:grey;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
31
src/sql/base/parts/tree/browser/loading-hc.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:white;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
31
src/sql/base/parts/tree/browser/loading.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/sql/base/parts/tree/browser/tree-collapsed-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69064L6.33333 3.02397L5.71461 3.64269L10.0719 7.99999Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
3
src/sql/base/parts/tree/browser/tree-collapsed-hc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
3
src/sql/base/parts/tree/browser/tree-collapsed-light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
3
src/sql/base/parts/tree/browser/tree-expanded-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97602 10.0719L12.3333 5.71461L12.952 6.33333L8.28538 11L7.66666 11L3 6.33333L3.61872 5.71461L7.97602 10.0719Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
3
src/sql/base/parts/tree/browser/tree-expanded-hc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97602 10.0719L12.3333 5.71461L12.952 6.33333L8.28538 11L7.66666 11L3 6.33333L3.61872 5.71461L7.97602 10.0719Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 281 B |
3
src/sql/base/parts/tree/browser/tree-expanded-light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97602 10.0719L12.3333 5.71461L12.952 6.33333L8.28538 11L7.66666 11L3 6.33333L3.61872 5.71461L7.97602 10.0719Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
112
src/sql/base/parts/tree/browser/tree.css
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
.monaco-tree {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-tree > .monaco-scrollable-element {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-tree > .monaco-scrollable-element > .monaco-tree-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-tree-drag-image {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* for OS X ballistic scrolling */
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row.scrolling {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Highlighted */
|
||||
|
||||
.monaco-tree.highlighted .monaco-tree-rows > .monaco-tree-row:not(.highlighted) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Expansion */
|
||||
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
display: block;
|
||||
background: url('tree-collapsed-light.svg') 50% 50% no-repeat;
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -16px;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded > .content:before {
|
||||
background-image: url('tree-expanded-light.svg');
|
||||
}
|
||||
|
||||
.monaco-tree.focused .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children.selected.expanded:not(.loading) > .content:before {
|
||||
background-image: url('tree-expanded-hc.svg');
|
||||
}
|
||||
|
||||
.monaco-tree.focused .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children.selected:not(.loading) > .content:before {
|
||||
background-image: url('tree-collapsed-hc.svg');
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before {
|
||||
background-image: url('loading.svg');
|
||||
}
|
||||
.vs-dark .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
|
||||
background-image: url('tree-collapsed-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded > .content:before {
|
||||
background-image: url('tree-expanded-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before {
|
||||
background-image: url('loading-dark.svg');
|
||||
}
|
||||
|
||||
.hc-black .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
|
||||
background-image: url('tree-collapsed-hc.svg');
|
||||
}
|
||||
|
||||
.hc-black .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded > .content:before {
|
||||
background-image: url('tree-expanded-hc.svg');
|
||||
}
|
||||
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before {
|
||||
background-image: url('loading-hc.svg');
|
||||
}
|
||||
620
src/sql/base/parts/tree/browser/tree.ts
Normal file
@@ -0,0 +1,620 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as Touch from 'vs/base/browser/touch';
|
||||
import * as Mouse from 'vs/base/browser/mouseEvent';
|
||||
import * as Keyboard from 'vs/base/browser/keyboardEvent';
|
||||
import { INavigator } from 'sql/base/common/navigator';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IItemCollapseEvent, IItemExpandEvent } from 'sql/base/parts/tree/browser/treeModel';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
export interface ITree {
|
||||
|
||||
onDidFocus: Event<void>;
|
||||
onDidBlur: Event<void>;
|
||||
onDidChangeFocus: Event<IFocusEvent>;
|
||||
onDidChangeSelection: Event<ISelectionEvent>;
|
||||
onDidChangeHighlight: Event<IHighlightEvent>;
|
||||
onDidExpandItem: Event<IItemExpandEvent>;
|
||||
onDidCollapseItem: Event<IItemCollapseEvent>;
|
||||
onDidDispose: Event<void>;
|
||||
onDidScroll: Event<void>;
|
||||
|
||||
/**
|
||||
* Returns the tree's DOM element.
|
||||
*/
|
||||
getHTMLElement(): HTMLElement;
|
||||
|
||||
/**
|
||||
* Lays out the tree.
|
||||
* Provide a specific height to save an (expensive) height computation.
|
||||
*/
|
||||
layout(height?: number): void;
|
||||
|
||||
/**
|
||||
* Notifies the tree that is has become visible.
|
||||
*/
|
||||
onVisible(): void;
|
||||
|
||||
/**
|
||||
* Notifies the tree that is has become hidden.
|
||||
*/
|
||||
onHidden(): void;
|
||||
|
||||
/**
|
||||
* Sets the input of the tree.
|
||||
*/
|
||||
setInput(element: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Returns the tree's input.
|
||||
*/
|
||||
getInput(): any;
|
||||
|
||||
/**
|
||||
* Sets DOM focus on the tree.
|
||||
*/
|
||||
domFocus(): void;
|
||||
|
||||
/**
|
||||
* Returns whether the tree has DOM focus.
|
||||
*/
|
||||
isDOMFocused(): boolean;
|
||||
|
||||
/**
|
||||
* Removes DOM focus from the tree.
|
||||
*/
|
||||
domBlur(): void;
|
||||
|
||||
/**
|
||||
* Refreshes an element.
|
||||
* Provide no arguments and it will refresh the input element.
|
||||
*/
|
||||
refresh(element?: any, recursive?: boolean): Promise<any>;
|
||||
|
||||
/**
|
||||
* Expands an element.
|
||||
* The returned promise returns a boolean for whether the element was expanded or not.
|
||||
*/
|
||||
expand(element: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Expands several elements.
|
||||
* The returned promise returns a boolean array for whether the elements were expanded or not.
|
||||
*/
|
||||
expandAll(elements?: any[]): Promise<any>;
|
||||
|
||||
/**
|
||||
* Collapses an element.
|
||||
* The returned promise returns a boolean for whether the element was collapsed or not.
|
||||
*/
|
||||
collapse(element: any, recursive?: boolean): Promise<any>;
|
||||
|
||||
/**
|
||||
* Collapses several elements.
|
||||
* Provide no arguments and it will recursively collapse all elements in the tree
|
||||
* The returned promise returns a boolean for whether the elements were collapsed or not.
|
||||
*/
|
||||
collapseAll(elements?: any[], recursive?: boolean): Promise<any>;
|
||||
|
||||
/**
|
||||
* Toggles an element's expansion state.
|
||||
*/
|
||||
toggleExpansion(element: any, recursive?: boolean): Promise<any>;
|
||||
|
||||
/**
|
||||
* Returns whether an element is expanded or not.
|
||||
*/
|
||||
isExpanded(element: any): boolean;
|
||||
|
||||
/**
|
||||
* Reveals an element in the tree. The relativeTop is a value between 0 and 1. The closer to 0 the more the
|
||||
* element will scroll up to the top.
|
||||
*/
|
||||
reveal(element: any, relativeTop?: number): Promise<any>;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the currently highlighted element.
|
||||
*/
|
||||
getHighlight(includeHidden?: boolean): any;
|
||||
|
||||
/**
|
||||
* Clears the highlight.
|
||||
*/
|
||||
clearHighlight(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Replaces the current selection with the given elements.
|
||||
*/
|
||||
setSelection(elements: any[], eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Returns the currently selected elements.
|
||||
*/
|
||||
getSelection(includeHidden?: boolean): any[];
|
||||
|
||||
/**
|
||||
* Clears the selection.
|
||||
*/
|
||||
clearSelection(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Sets the focused element.
|
||||
*/
|
||||
setFocus(element?: any, eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Returns focused element.
|
||||
*/
|
||||
getFocus(includeHidden?: boolean): any;
|
||||
|
||||
/**
|
||||
* Focuses the next `count`-nth element, in visible order.
|
||||
*/
|
||||
focusNext(count?: number, eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the previous `count`-nth element, in visible order.
|
||||
*/
|
||||
focusPrevious(count?: number, eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the currently focused element's parent.
|
||||
*/
|
||||
focusParent(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the first child of the currently focused element.
|
||||
*/
|
||||
focusFirstChild(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the second element, in visible order. Will focus the first
|
||||
* child from the provided element's parent if any.
|
||||
*/
|
||||
focusFirst(eventPayload?: any, from?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the nth element, in visible order.
|
||||
*/
|
||||
focusNth(index: number, eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the last element, in visible order. Will focus the last
|
||||
* child from the provided element's parent if any.
|
||||
*/
|
||||
focusLast(eventPayload?: any, from?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the element at the end of the next page, in visible order.
|
||||
*/
|
||||
focusNextPage(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Focuses the element at the beginning of the previous page, in visible order.
|
||||
*/
|
||||
focusPreviousPage(eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Clears the focus.
|
||||
*/
|
||||
clearFocus(eventPayload?: any): void;
|
||||
|
||||
// @todo anthonydresser 4/12/19 we need to refactor our code to not need these methods
|
||||
/**
|
||||
* Adds the trait to elements.
|
||||
*/
|
||||
addTraits(trait: string, elements: any[]): void;
|
||||
|
||||
/**
|
||||
* Removes the trait from elements.
|
||||
*/
|
||||
removeTraits(trait: string, elements: any[]): void;
|
||||
|
||||
/**
|
||||
* Selects an element.
|
||||
*/
|
||||
select(element: any, eventPayload?: any): void;
|
||||
|
||||
/**
|
||||
* Deselects an element.
|
||||
*/
|
||||
deselect(element: any, eventPayload?: any): void;
|
||||
// END @todo
|
||||
|
||||
/**
|
||||
* Returns a navigator which allows to discover the visible and
|
||||
* expanded elements in the tree.
|
||||
*/
|
||||
getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator<any>;
|
||||
|
||||
/**
|
||||
* Apply styles to the tree.
|
||||
*/
|
||||
style(styles: ITreeStyles): void;
|
||||
|
||||
/**
|
||||
* Disposes the tree
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IDataSource {
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the given element.
|
||||
* No more than one element may use a given identifier.
|
||||
*
|
||||
* You should not attempt to "move" an element to a different
|
||||
* parent by keeping its ID. The idea here is to have tree location
|
||||
* related IDs (e.g. full file path, in the Explorer example).
|
||||
*/
|
||||
getId(tree: ITree, element: any): string;
|
||||
|
||||
/**
|
||||
* Returns a boolean value indicating whether the element has children.
|
||||
*/
|
||||
hasChildren(tree: ITree, element: any): boolean;
|
||||
|
||||
/**
|
||||
* Returns the element's children as an array in a promise.
|
||||
*/
|
||||
getChildren(tree: ITree, element: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
getParent(tree: ITree, element: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Returns whether an element should be expanded when first added to the tree.
|
||||
*/
|
||||
shouldAutoexpand?(tree: ITree, element: any): boolean;
|
||||
}
|
||||
|
||||
export interface IRenderer {
|
||||
|
||||
/**
|
||||
* Returns the element's height in the tree, in pixels.
|
||||
*/
|
||||
getHeight(tree: ITree, element: any): number;
|
||||
|
||||
/**
|
||||
* Returns a template ID for a given element. This will be used as an identifier
|
||||
* for the next 3 methods.
|
||||
*/
|
||||
getTemplateId(tree: ITree, element: any): string;
|
||||
|
||||
/**
|
||||
* Renders the template in a DOM element. This method should render all the DOM
|
||||
* structure for an hypothetical element leaving its contents blank. It should
|
||||
* return an object bag which will be passed along to `renderElement` and used
|
||||
* to fill in those blanks.
|
||||
*
|
||||
* You should do all DOM creating and object allocation in this method. It
|
||||
* will be called only a few times.
|
||||
*/
|
||||
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any;
|
||||
|
||||
/**
|
||||
* Renders an element, given an object bag returned by `renderTemplate`.
|
||||
* This method should do as little as possible and ideally it should only fill
|
||||
* in the blanks left by `renderTemplate`.
|
||||
*
|
||||
* Try to make this method do as little possible, since it will be called very
|
||||
* often.
|
||||
*/
|
||||
renderElement(tree: ITree, element: any, templateId: string, templateData: any): void;
|
||||
|
||||
/**
|
||||
* Disposes a template that was once rendered.
|
||||
*/
|
||||
disposeTemplate(tree: ITree, templateId: string, templateData: any): void;
|
||||
}
|
||||
|
||||
export interface IAccessibilityProvider {
|
||||
|
||||
/**
|
||||
* Given an element in the tree, return the ARIA label that should be associated with the
|
||||
* item. This helps screen readers to provide a meaningful label for the currently focused
|
||||
* tree element.
|
||||
*
|
||||
* Returning null will not disable ARIA for the element. Instead it is up to the screen reader
|
||||
* to compute a meaningful label based on the contents of the element in the DOM
|
||||
*
|
||||
* See also: https://www.w3.org/TR/wai-aria/states_and_properties#aria-label
|
||||
*/
|
||||
getAriaLabel(tree: ITree, element: any): string | null;
|
||||
|
||||
/**
|
||||
* Given an element in the tree return its aria-posinset. Should be between 1 and aria-setsize
|
||||
* https://www.w3.org/TR/wai-aria/states_and_properties#aria-posinset
|
||||
*/
|
||||
getPosInSet?(tree: ITree, element: any): string;
|
||||
|
||||
/**
|
||||
* Return the aria-setsize of the tree.
|
||||
* https://www.w3.org/TR/wai-aria/states_and_properties#aria-setsize
|
||||
*/
|
||||
getSetSize?(): string;
|
||||
}
|
||||
|
||||
export /* abstract */ class ContextMenuEvent {
|
||||
|
||||
private _posx: number;
|
||||
private _posy: number;
|
||||
private _target: HTMLElement;
|
||||
|
||||
constructor(posx: number, posy: number, target: HTMLElement) {
|
||||
this._posx = posx;
|
||||
this._posy = posy;
|
||||
this._target = target;
|
||||
}
|
||||
|
||||
public preventDefault(): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public stopPropagation(): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public get posx(): number {
|
||||
return this._posx;
|
||||
}
|
||||
|
||||
public get posy(): number {
|
||||
return this._posy;
|
||||
}
|
||||
|
||||
public get target(): HTMLElement {
|
||||
return this._target;
|
||||
}
|
||||
}
|
||||
|
||||
export class MouseContextMenuEvent extends ContextMenuEvent {
|
||||
|
||||
private originalEvent: Mouse.IMouseEvent;
|
||||
|
||||
constructor(originalEvent: Mouse.IMouseEvent) {
|
||||
super(originalEvent.posx, originalEvent.posy, originalEvent.target);
|
||||
this.originalEvent = originalEvent;
|
||||
}
|
||||
|
||||
public override preventDefault(): void {
|
||||
this.originalEvent.preventDefault();
|
||||
}
|
||||
|
||||
public override stopPropagation(): void {
|
||||
this.originalEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyboardContextMenuEvent extends ContextMenuEvent {
|
||||
|
||||
private originalEvent: Keyboard.IKeyboardEvent;
|
||||
|
||||
constructor(posx: number, posy: number, originalEvent: Keyboard.IKeyboardEvent) {
|
||||
super(posx, posy, originalEvent.target);
|
||||
this.originalEvent = originalEvent;
|
||||
}
|
||||
|
||||
public override preventDefault(): void {
|
||||
this.originalEvent.preventDefault();
|
||||
}
|
||||
|
||||
public override stopPropagation(): void {
|
||||
this.originalEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IController {
|
||||
|
||||
/**
|
||||
* Called when an element is clicked.
|
||||
*/
|
||||
onClick(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when an element is requested for a context menu.
|
||||
*/
|
||||
onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when an element is tapped.
|
||||
*/
|
||||
onTap(tree: ITree, element: any, event: Touch.GestureEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when a key is pressed down while selecting elements.
|
||||
*/
|
||||
onKeyDown(tree: ITree, event: Keyboard.IKeyboardEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when a key is released while selecting elements.
|
||||
*/
|
||||
onKeyUp(tree: ITree, event: Keyboard.IKeyboardEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when a mouse middle button is pressed down on an element.
|
||||
*/
|
||||
onMouseMiddleClick?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when a mouse button is pressed down on an element.
|
||||
*/
|
||||
onMouseDown?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean;
|
||||
|
||||
/**
|
||||
* Called when a mouse button goes up on an element.
|
||||
*/
|
||||
onMouseUp?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean;
|
||||
}
|
||||
|
||||
export const enum DragOverEffect {
|
||||
COPY,
|
||||
MOVE
|
||||
}
|
||||
|
||||
export const enum DragOverBubble {
|
||||
BUBBLE_DOWN,
|
||||
BUBBLE_UP
|
||||
}
|
||||
|
||||
export interface IDragOverReaction {
|
||||
accept: boolean;
|
||||
effect?: DragOverEffect;
|
||||
bubble?: DragOverBubble;
|
||||
autoExpand?: boolean;
|
||||
}
|
||||
|
||||
// @todo anthonydresser 4/12/19 refactor to not need this
|
||||
export const DRAG_OVER_REJECT: IDragOverReaction = { accept: false };
|
||||
export const DRAG_OVER_ACCEPT: IDragOverReaction = { accept: true };
|
||||
export const DRAG_OVER_ACCEPT_BUBBLE_UP: IDragOverReaction = { accept: true, bubble: DragOverBubble.BUBBLE_UP };
|
||||
export const DRAG_OVER_ACCEPT_BUBBLE_DOWN = (autoExpand = false) => ({ accept: true, bubble: DragOverBubble.BUBBLE_DOWN, autoExpand });
|
||||
export const DRAG_OVER_ACCEPT_BUBBLE_UP_COPY: IDragOverReaction = { accept: true, bubble: DragOverBubble.BUBBLE_UP, effect: DragOverEffect.COPY };
|
||||
export const DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY = (autoExpand = false) => ({ accept: true, bubble: DragOverBubble.BUBBLE_DOWN, effect: DragOverEffect.COPY, autoExpand });
|
||||
// END @todo
|
||||
|
||||
export interface IDragAndDrop {
|
||||
|
||||
/**
|
||||
* Returns a uri if the given element should be allowed to drag.
|
||||
* Returns null, otherwise.
|
||||
*/
|
||||
getDragURI(tree: ITree, element: any): string | null;
|
||||
|
||||
/**
|
||||
* Returns a label to display when dragging the element.
|
||||
*/
|
||||
getDragLabel?(tree: ITree, elements: any[]): string;
|
||||
|
||||
/**
|
||||
* Sent when the drag operation is starting.
|
||||
*/
|
||||
onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: Mouse.DragMouseEvent): void;
|
||||
|
||||
/**
|
||||
* Returns a DragOverReaction indicating whether sources can be
|
||||
* dropped into target or some parent of the target.
|
||||
*/
|
||||
onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): IDragOverReaction | null;
|
||||
|
||||
/**
|
||||
* Handles the action of dropping sources into target.
|
||||
*/
|
||||
drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): void;
|
||||
|
||||
/**
|
||||
* Handles the action of canceled drag-and-drop activities
|
||||
*/
|
||||
dropAbort(tree: ITree, data: IDragAndDropData): void;
|
||||
|
||||
}
|
||||
|
||||
export interface IFilter {
|
||||
|
||||
/**
|
||||
* Returns whether the given element should be visible.
|
||||
*/
|
||||
isVisible(tree: ITree, element: any): boolean;
|
||||
}
|
||||
|
||||
export interface ISorter {
|
||||
|
||||
/**
|
||||
* Compare two elements in the viewer to define the sorting order.
|
||||
*/
|
||||
compare(tree: ITree, element: any, otherElement: any): number;
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
export interface ISelectionEvent {
|
||||
selection: any[];
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface IFocusEvent {
|
||||
focus: any;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface IHighlightEvent {
|
||||
highlight: any;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
// Options
|
||||
|
||||
export interface ITreeConfiguration {
|
||||
dataSource: IDataSource;
|
||||
renderer?: IRenderer;
|
||||
controller?: IController;
|
||||
dnd?: IDragAndDrop;
|
||||
filter?: IFilter;
|
||||
sorter?: ISorter;
|
||||
accessibilityProvider?: IAccessibilityProvider;
|
||||
styler?: ITreeStyler;
|
||||
}
|
||||
|
||||
export interface ITreeOptions extends ITreeStyles {
|
||||
twistiePixels?: number;
|
||||
showTwistie?: boolean;
|
||||
indentPixels?: number;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
horizontalScrollMode?: ScrollbarVisibility;
|
||||
alwaysFocused?: boolean;
|
||||
autoExpandSingleChildren?: boolean;
|
||||
useShadows?: boolean;
|
||||
paddingOnRow?: boolean;
|
||||
ariaLabel?: string;
|
||||
keyboardSupport?: boolean;
|
||||
preventRootFocus?: boolean;
|
||||
showLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface ITreeStyler {
|
||||
style(styles: ITreeStyles): void;
|
||||
}
|
||||
|
||||
export interface ITreeStyles {
|
||||
listFocusBackground?: Color;
|
||||
listFocusForeground?: Color;
|
||||
listActiveSelectionBackground?: Color;
|
||||
listActiveSelectionForeground?: Color;
|
||||
listFocusAndSelectionBackground?: Color;
|
||||
listFocusAndSelectionForeground?: Color;
|
||||
listInactiveSelectionBackground?: Color;
|
||||
listInactiveSelectionForeground?: Color;
|
||||
listHoverBackground?: Color;
|
||||
listHoverForeground?: Color;
|
||||
listDropBackground?: Color;
|
||||
listFocusOutline?: Color;
|
||||
}
|
||||
|
||||
export interface ITreeContext extends ITreeConfiguration {
|
||||
tree: ITree;
|
||||
options: ITreeOptions;
|
||||
}
|
||||
|
||||
export interface IActionProvider {
|
||||
|
||||
/**
|
||||
* Returns whether or not the element has actions. These show up in place right to the element in the tree.
|
||||
*/
|
||||
hasActions(tree: ITree | null, element: any): boolean;
|
||||
|
||||
/**
|
||||
* Returns an array with the actions of the element that should show up in place right to the element in the tree.
|
||||
*/
|
||||
getActions(tree: ITree | null, element: any): ReadonlyArray<IAction> | null;
|
||||
}
|
||||
577
src/sql/base/parts/tree/browser/treeDefaults.ts
Normal file
@@ -0,0 +1,577 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as touch from 'vs/base/browser/touch';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as mouse from 'vs/base/browser/mouseEvent';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import * as _ from 'sql/base/parts/tree/browser/tree';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { createKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keybindings';
|
||||
|
||||
export interface IKeyBindingCallback {
|
||||
(tree: _.ITree, event: IKeyboardEvent): void;
|
||||
}
|
||||
|
||||
export interface ICancelableEvent {
|
||||
preventDefault(): void;
|
||||
stopPropagation(): void;
|
||||
}
|
||||
|
||||
export const enum ClickBehavior {
|
||||
|
||||
/**
|
||||
* Handle the click when the mouse button is pressed but not released yet.
|
||||
*/
|
||||
ON_MOUSE_DOWN,
|
||||
|
||||
/**
|
||||
* Handle the click when the mouse button is released.
|
||||
*/
|
||||
ON_MOUSE_UP
|
||||
}
|
||||
|
||||
export const enum OpenMode {
|
||||
SINGLE_CLICK,
|
||||
DOUBLE_CLICK
|
||||
}
|
||||
|
||||
export interface IControllerOptions {
|
||||
clickBehavior?: ClickBehavior;
|
||||
openMode?: OpenMode;
|
||||
keyboardSupport?: boolean;
|
||||
}
|
||||
|
||||
interface IKeybindingDispatcherItem {
|
||||
keybinding: Keybinding | null;
|
||||
callback: IKeyBindingCallback;
|
||||
}
|
||||
|
||||
export class KeybindingDispatcher {
|
||||
|
||||
private _arr: IKeybindingDispatcherItem[];
|
||||
|
||||
constructor() {
|
||||
this._arr = [];
|
||||
}
|
||||
|
||||
public has(keybinding: KeyCode): boolean {
|
||||
let target = createKeybinding(keybinding, platform.OS);
|
||||
if (target !== null) {
|
||||
for (const a of this._arr) {
|
||||
if (target.equals(a.keybinding)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public set(keybinding: number, callback: IKeyBindingCallback) {
|
||||
this._arr.push({
|
||||
keybinding: createKeybinding(keybinding, platform.OS),
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
public dispatch(keybinding: SimpleKeybinding): IKeyBindingCallback | null {
|
||||
// Loop from the last to the first to handle overwrites
|
||||
for (let i = this._arr.length - 1; i >= 0; i--) {
|
||||
let item = this._arr[i];
|
||||
if (keybinding.toChord().equals(item.keybinding)) {
|
||||
return item.callback;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultController implements _.IController {
|
||||
|
||||
protected downKeyBindingDispatcher: KeybindingDispatcher;
|
||||
protected upKeyBindingDispatcher: KeybindingDispatcher;
|
||||
|
||||
private options: IControllerOptions;
|
||||
|
||||
constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) {
|
||||
this.options = options;
|
||||
|
||||
this.downKeyBindingDispatcher = new KeybindingDispatcher();
|
||||
this.upKeyBindingDispatcher = new KeybindingDispatcher();
|
||||
|
||||
if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) {
|
||||
this.downKeyBindingDispatcher.set(KeyCode.UpArrow, (t, e) => this.onUp(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.DownArrow, (t, e) => this.onDown(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.LeftArrow, (t, e) => this.onLeft(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.RightArrow, (t, e) => this.onRight(t, e));
|
||||
if (platform.isMacintosh) {
|
||||
this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.UpArrow, (t, e) => this.onLeft(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KeyN, (t, e) => this.onDown(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KeyP, (t, e) => this.onUp(t, e));
|
||||
}
|
||||
this.downKeyBindingDispatcher.set(KeyCode.PageUp, (t, e) => this.onPageUp(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.PageDown, (t, e) => this.onPageDown(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.Home, (t, e) => this.onHome(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.End, (t, e) => this.onEnd(t, e));
|
||||
|
||||
this.downKeyBindingDispatcher.set(KeyCode.Space, (t, e) => this.onSpace(t, e));
|
||||
this.downKeyBindingDispatcher.set(KeyCode.Escape, (t, e) => this.onEscape(t, e));
|
||||
|
||||
this.upKeyBindingDispatcher.set(KeyCode.Enter, this.onEnter.bind(this));
|
||||
this.upKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, this.onEnter.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
public onMouseDown(tree: _.ITree, element: any, event: mouse.IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) {
|
||||
if (event.target) {
|
||||
if (event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false; // Ignore event if target is a form input field (avoids browser specific issues)
|
||||
}
|
||||
|
||||
if (dom.findParentWithClass(event.target, 'scrollbar', 'monaco-tree')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dom.findParentWithClass(event.target, 'monaco-action-bar', 'row')) { // TODO@Joao not very nice way of checking for the action bar (implicit knowledge)
|
||||
return false; // Ignore event if target is over an action bar of the row
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate to onLeftClick now
|
||||
return this.onLeftClick(tree, element, event, origin);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public onClick(tree: _.ITree, element: any, event: mouse.IMouseEvent): boolean {
|
||||
const isMac = platform.isMacintosh;
|
||||
|
||||
// A Ctrl click on the Mac is a context menu event
|
||||
if (isMac && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false; // Ignore event if target is a form input field (avoids browser specific issues)
|
||||
}
|
||||
|
||||
if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) {
|
||||
return false; // Already handled by onMouseDown
|
||||
}
|
||||
|
||||
return this.onLeftClick(tree, element, event);
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
|
||||
const event = <mouse.IMouseEvent>eventish;
|
||||
const payload = { origin: origin, originalEvent: eventish, didClickOnTwistie: this.isClickOnTwistie(event) };
|
||||
|
||||
if (tree.getInput() === element) {
|
||||
tree.clearFocus(payload);
|
||||
tree.clearSelection(payload);
|
||||
} else {
|
||||
const isSingleMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown' && event.browserEvent.detail === 1;
|
||||
if (!isSingleMouseDown) {
|
||||
eventish.preventDefault(); // we cannot preventDefault onMouseDown with single click because this would break DND otherwise
|
||||
}
|
||||
eventish.stopPropagation();
|
||||
|
||||
tree.domFocus();
|
||||
tree.setSelection([element], payload);
|
||||
tree.setFocus(element, payload);
|
||||
|
||||
if (this.shouldToggleExpansion(element, event, origin)) {
|
||||
if (tree.isExpanded(element)) {
|
||||
tree.collapse(element).then(undefined, errors.onUnexpectedError);
|
||||
} else {
|
||||
tree.expand(element).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected shouldToggleExpansion(element: any, event: mouse.IMouseEvent, origin: string): boolean {
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
return this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event);
|
||||
}
|
||||
|
||||
protected setOpenMode(openMode: OpenMode) {
|
||||
this.options.openMode = openMode;
|
||||
}
|
||||
|
||||
protected get openOnSingleClick(): boolean {
|
||||
return this.options.openMode === OpenMode.SINGLE_CLICK;
|
||||
}
|
||||
|
||||
protected isClickOnTwistie(event: mouse.IMouseEvent): boolean {
|
||||
let element = event.target as HTMLElement;
|
||||
|
||||
if (!element.classList.contains('content')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const twistieStyle = window.getComputedStyle(element, ':before');
|
||||
|
||||
if (twistieStyle.backgroundImage === 'none' || twistieStyle.display === 'none') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const twistieWidth = parseInt(twistieStyle.width!) + parseInt(twistieStyle.paddingRight!);
|
||||
return event.browserEvent.offsetX <= twistieWidth;
|
||||
}
|
||||
|
||||
public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean {
|
||||
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false; // allow context menu on input fields
|
||||
}
|
||||
|
||||
// Prevent native context menu from showing up
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public onTap(tree: _.ITree, element: any, event: touch.GestureEvent): boolean {
|
||||
const target = <HTMLElement>event.initialTarget;
|
||||
|
||||
if (target && target.tagName && target.tagName.toLowerCase() === 'input') {
|
||||
return false; // Ignore event if target is a form input field (avoids browser specific issues)
|
||||
}
|
||||
|
||||
return this.onLeftClick(tree, element, event, 'touch');
|
||||
}
|
||||
|
||||
public onKeyDown(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
return this.onKey(this.downKeyBindingDispatcher, tree, event);
|
||||
}
|
||||
|
||||
public onKeyUp(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
return this.onKey(this.upKeyBindingDispatcher, tree, event);
|
||||
}
|
||||
|
||||
private onKey(bindings: KeybindingDispatcher, tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const handler: any = bindings.dispatch(event.toKeybinding());
|
||||
if (handler) {
|
||||
// TODO: TS 3.1 upgrade. Why are we checking against void?
|
||||
if (handler(tree, event)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected onUp(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusPrevious(1, payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onPageUp(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusPreviousPage(payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onDown(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusNext(1, payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onPageDown(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusNextPage(payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onHome(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusFirst(payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onEnd(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
tree.focusLast(payload);
|
||||
tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onLeft(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
const focus = tree.getFocus();
|
||||
tree.collapse(focus).then(didCollapse => {
|
||||
if (focus && !didCollapse) {
|
||||
tree.focusParent(payload);
|
||||
return tree.reveal(tree.getFocus());
|
||||
}
|
||||
return undefined;
|
||||
}).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onRight(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
const focus = tree.getFocus();
|
||||
tree.expand(focus).then(didExpand => {
|
||||
if (focus && !didExpand) {
|
||||
tree.focusFirstChild(payload);
|
||||
return tree.reveal(tree.getFocus());
|
||||
}
|
||||
return undefined;
|
||||
}).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onEnter(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
return false;
|
||||
}
|
||||
const focus = tree.getFocus();
|
||||
if (focus) {
|
||||
tree.setSelection([focus], payload);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onSpace(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
if (tree.getHighlight()) {
|
||||
return false;
|
||||
}
|
||||
const focus = tree.getFocus();
|
||||
if (focus) {
|
||||
tree.toggleExpansion(focus);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onEscape(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tree.getSelection().length) {
|
||||
tree.clearSelection(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tree.getFocus()) {
|
||||
tree.clearFocus(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultDragAndDrop implements _.IDragAndDrop {
|
||||
|
||||
public getDragURI(tree: _.ITree, element: any): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public onDragStart(tree: _.ITree, data: IDragAndDropData, originalEvent: mouse.DragMouseEvent): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public onDragOver(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): _.IDragOverReaction | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public drop(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public dropAbort(tree: _.ITree, data: IDragAndDropData): void { }
|
||||
}
|
||||
|
||||
export class DefaultFilter implements _.IFilter {
|
||||
|
||||
public isVisible(tree: _.ITree, element: any): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultSorter implements _.ISorter {
|
||||
|
||||
public compare(tree: _.ITree, element: any, otherElement: any): number {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultAccessibilityProvider implements _.IAccessibilityProvider {
|
||||
|
||||
getAriaLabel(tree: _.ITree, element: any): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultTreestyler implements _.ITreeStyler {
|
||||
|
||||
constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { }
|
||||
|
||||
style(styles: _.ITreeStyles): void {
|
||||
const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : '';
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.listFocusBackground) {
|
||||
content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: ${styles.listFocusBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listFocusForeground) {
|
||||
content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { color: ${styles.listFocusForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listActiveSelectionBackground) {
|
||||
content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listActiveSelectionBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listActiveSelectionForeground) {
|
||||
content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listActiveSelectionForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listFocusAndSelectionBackground) {
|
||||
content.push(`
|
||||
.monaco-tree-drag-image,
|
||||
.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: ${styles.listFocusAndSelectionBackground}; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listFocusAndSelectionForeground) {
|
||||
content.push(`
|
||||
.monaco-tree-drag-image,
|
||||
.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { color: ${styles.listFocusAndSelectionForeground}; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listInactiveSelectionBackground) {
|
||||
content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listInactiveSelectionBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listInactiveSelectionForeground) {
|
||||
content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listInactiveSelectionForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listHoverBackground) {
|
||||
content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listHoverForeground) {
|
||||
content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listDropBackground) {
|
||||
content.push(`
|
||||
.monaco-tree${suffix} .monaco-tree-wrapper.drop-target,
|
||||
.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listFocusOutline) {
|
||||
content.push(`
|
||||
.monaco-tree-drag-image { border: 1px solid ${styles.listFocusOutline}; background: #000; }
|
||||
.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row { border: 1px solid transparent; }
|
||||
.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted ${styles.listFocusOutline}; }
|
||||
.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; }
|
||||
.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; }
|
||||
.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed ${styles.listFocusOutline}; }
|
||||
.monaco-tree${suffix} .monaco-tree-wrapper.drop-target,
|
||||
.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { border: 1px dashed ${styles.listFocusOutline}; }
|
||||
`);
|
||||
}
|
||||
|
||||
const newStyles = content.join('\n');
|
||||
if (newStyles !== this.styleElement.innerHTML) {
|
||||
this.styleElement.innerHTML = newStyles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CollapseAllAction extends Action {
|
||||
|
||||
constructor(private viewer: _.ITree, enabled: boolean) {
|
||||
super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled);
|
||||
}
|
||||
|
||||
public override run(context?: any): Promise<any> {
|
||||
if (this.viewer.getHighlight()) {
|
||||
return Promise.resolve(); // Global action disabled if user is in edit mode from another action
|
||||
}
|
||||
|
||||
this.viewer.collapseAll();
|
||||
this.viewer.clearSelection();
|
||||
this.viewer.clearFocus();
|
||||
this.viewer.domFocus();
|
||||
this.viewer.focusFirst();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
73
src/sql/base/parts/tree/browser/treeDnd.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as _ from 'sql/base/parts/tree/browser/tree';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
export class ElementsDragAndDropData implements IDragAndDropData {
|
||||
|
||||
private elements: any[];
|
||||
|
||||
constructor(elements: any[]) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public update(dataTransfer: DataTransfer): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public getData(): any {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalElementsDragAndDropData implements IDragAndDropData {
|
||||
|
||||
private elements: any[];
|
||||
|
||||
constructor(elements: any[]) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public update(dataTransfer: DataTransfer): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public getData(): any {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class DesktopDragAndDropData implements IDragAndDropData {
|
||||
|
||||
private types: any[];
|
||||
private files: any[];
|
||||
|
||||
constructor() {
|
||||
this.types = [];
|
||||
this.files = [];
|
||||
}
|
||||
|
||||
public update(dataTransfer: DataTransfer): void {
|
||||
if (dataTransfer.types) {
|
||||
this.types = [];
|
||||
Array.prototype.push.apply(this.types, dataTransfer.types as any);
|
||||
}
|
||||
|
||||
if (dataTransfer.files) {
|
||||
this.files = [];
|
||||
Array.prototype.push.apply(this.files, dataTransfer.files as any);
|
||||
|
||||
this.files = this.files.filter(f => f.size || f.type);
|
||||
}
|
||||
}
|
||||
|
||||
public getData(): any {
|
||||
return {
|
||||
types: this.types,
|
||||
files: this.files
|
||||
};
|
||||
}
|
||||
}
|
||||
316
src/sql/base/parts/tree/browser/treeImpl.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./tree';
|
||||
import * as TreeDefaults from 'sql/base/parts/tree/browser/treeDefaults';
|
||||
import * as Model from 'sql/base/parts/tree/browser/treeModel';
|
||||
import * as View from './treeView';
|
||||
import * as _ from 'sql/base/parts/tree/browser/tree';
|
||||
import { INavigator, MappedNavigator } from 'sql/base/common/navigator';
|
||||
import { Event, Emitter, Relay } from 'vs/base/common/event';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
export class TreeContext implements _.ITreeContext {
|
||||
|
||||
public tree: _.ITree;
|
||||
public configuration: _.ITreeConfiguration;
|
||||
public options: _.ITreeOptions;
|
||||
|
||||
public dataSource: _.IDataSource;
|
||||
public renderer?: _.IRenderer;
|
||||
public controller: _.IController;
|
||||
public dnd: _.IDragAndDrop;
|
||||
public filter: _.IFilter;
|
||||
public sorter?: _.ISorter;
|
||||
public accessibilityProvider: _.IAccessibilityProvider;
|
||||
public styler?: _.ITreeStyler;
|
||||
|
||||
constructor(tree: _.ITree, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) {
|
||||
this.tree = tree;
|
||||
this.configuration = configuration;
|
||||
this.options = options;
|
||||
|
||||
if (!configuration.dataSource) {
|
||||
throw new Error('You must provide a Data Source to the tree.');
|
||||
}
|
||||
|
||||
this.dataSource = configuration.dataSource;
|
||||
this.renderer = configuration.renderer;
|
||||
this.controller = configuration.controller || new TreeDefaults.DefaultController({ clickBehavior: TreeDefaults.ClickBehavior.ON_MOUSE_UP, keyboardSupport: typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport });
|
||||
this.dnd = configuration.dnd || new TreeDefaults.DefaultDragAndDrop();
|
||||
this.filter = configuration.filter || new TreeDefaults.DefaultFilter();
|
||||
this.sorter = configuration.sorter;
|
||||
this.accessibilityProvider = configuration.accessibilityProvider || new TreeDefaults.DefaultAccessibilityProvider();
|
||||
this.styler = configuration.styler;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultStyles: _.ITreeStyles = {
|
||||
listFocusBackground: Color.fromHex('#073655'),
|
||||
listActiveSelectionBackground: Color.fromHex('#0E639C'),
|
||||
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
|
||||
listFocusAndSelectionBackground: Color.fromHex('#094771'),
|
||||
listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'),
|
||||
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
|
||||
listHoverBackground: Color.fromHex('#2A2D2E'),
|
||||
listDropBackground: Color.fromHex('#383B3D')
|
||||
};
|
||||
|
||||
export class Tree implements _.ITree {
|
||||
|
||||
private container: HTMLElement;
|
||||
|
||||
private context: _.ITreeContext;
|
||||
private model: Model.TreeModel;
|
||||
private view: View.TreeView;
|
||||
|
||||
private _onDidChangeFocus = new Relay<_.IFocusEvent>();
|
||||
readonly onDidChangeFocus: Event<_.IFocusEvent> = this._onDidChangeFocus.event;
|
||||
private _onDidChangeSelection = new Relay<_.ISelectionEvent>();
|
||||
readonly onDidChangeSelection: Event<_.ISelectionEvent> = this._onDidChangeSelection.event;
|
||||
private _onHighlightChange = new Relay<_.IHighlightEvent>();
|
||||
readonly onDidChangeHighlight: Event<_.IHighlightEvent> = this._onHighlightChange.event;
|
||||
private _onDidExpandItem = new Relay<Model.IItemExpandEvent>();
|
||||
readonly onDidExpandItem: Event<Model.IItemExpandEvent> = this._onDidExpandItem.event;
|
||||
private _onDidCollapseItem = new Relay<Model.IItemCollapseEvent>();
|
||||
readonly onDidCollapseItem: Event<Model.IItemCollapseEvent> = this._onDidCollapseItem.event;
|
||||
private readonly _onDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDispose.event;
|
||||
|
||||
constructor(container: HTMLElement, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) {
|
||||
this.container = container;
|
||||
mixin(options, defaultStyles, false);
|
||||
|
||||
options.twistiePixels = typeof options.twistiePixels === 'number' ? options.twistiePixels : 32;
|
||||
options.showTwistie = options.showTwistie === false ? false : true;
|
||||
options.indentPixels = typeof options.indentPixels === 'number' ? options.indentPixels : 12;
|
||||
options.alwaysFocused = options.alwaysFocused === true ? true : false;
|
||||
options.useShadows = options.useShadows === false ? false : true;
|
||||
options.paddingOnRow = options.paddingOnRow === false ? false : true;
|
||||
options.showLoading = options.showLoading === false ? false : true;
|
||||
|
||||
this.context = new TreeContext(this, configuration, options);
|
||||
this.model = new Model.TreeModel(this.context);
|
||||
this.view = new View.TreeView(this.context, this.container);
|
||||
|
||||
this.view.setModel(this.model);
|
||||
|
||||
this._onDidChangeFocus.input = this.model.onDidFocus;
|
||||
this._onDidChangeSelection.input = this.model.onDidSelect;
|
||||
this._onHighlightChange.input = this.model.onDidHighlight;
|
||||
this._onDidExpandItem.input = this.model.onDidExpandItem;
|
||||
this._onDidCollapseItem.input = this.model.onDidCollapseItem;
|
||||
}
|
||||
|
||||
public style(styles: _.ITreeStyles): void {
|
||||
this.view.applyStyles(styles);
|
||||
}
|
||||
|
||||
get onDidFocus(): Event<void> {
|
||||
return this.view.onDOMFocus;
|
||||
}
|
||||
|
||||
get onDidBlur(): Event<void> {
|
||||
return this.view.onDOMBlur;
|
||||
}
|
||||
|
||||
get onDidScroll(): Event<void> {
|
||||
return this.view.onDidScroll;
|
||||
}
|
||||
|
||||
public getHTMLElement(): HTMLElement {
|
||||
return this.view.getHTMLElement();
|
||||
}
|
||||
|
||||
public layout(height?: number, width?: number): void {
|
||||
this.view.layout(height, width);
|
||||
}
|
||||
|
||||
public domFocus(): void {
|
||||
this.view.focus();
|
||||
}
|
||||
|
||||
public isDOMFocused(): boolean {
|
||||
return this.view.isFocused();
|
||||
}
|
||||
|
||||
public domBlur(): void {
|
||||
this.view.blur();
|
||||
}
|
||||
|
||||
public onVisible(): void {
|
||||
this.view.onVisible();
|
||||
}
|
||||
|
||||
public onHidden(): void {
|
||||
this.view.onHidden();
|
||||
}
|
||||
|
||||
public setInput(element: any): Promise<any> {
|
||||
return this.model.setInput(element);
|
||||
}
|
||||
|
||||
public getInput(): any {
|
||||
return this.model.getInput();
|
||||
}
|
||||
|
||||
public refresh(element: any = null, recursive = true): Promise<any> {
|
||||
return this.model.refresh(element, recursive);
|
||||
}
|
||||
|
||||
public expand(element: any): Promise<any> {
|
||||
return this.model.expand(element);
|
||||
}
|
||||
|
||||
public expandAll(elements: any[]): Promise<any> {
|
||||
return this.model.expandAll(elements);
|
||||
}
|
||||
|
||||
public collapse(element: any, recursive: boolean = false): Promise<any> {
|
||||
return this.model.collapse(element, recursive);
|
||||
}
|
||||
|
||||
public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise<any> {
|
||||
return this.model.collapseAll(elements, recursive);
|
||||
}
|
||||
|
||||
public toggleExpansion(element: any, recursive: boolean = false): Promise<any> {
|
||||
return this.model.toggleExpansion(element, recursive);
|
||||
}
|
||||
|
||||
public isExpanded(element: any): boolean {
|
||||
return this.model.isExpanded(element);
|
||||
}
|
||||
|
||||
public reveal(element: any, relativeTop: number | null = null): Promise<any> {
|
||||
return this.model.reveal(element, relativeTop);
|
||||
}
|
||||
|
||||
public getExpandedElements(): any[] {
|
||||
return this.model.getExpandedElements();
|
||||
}
|
||||
|
||||
public getScrollPosition(): number {
|
||||
return this.view.getScrollPosition();
|
||||
}
|
||||
|
||||
public setScrollPosition(pos: number): void {
|
||||
this.view.setScrollPosition(pos);
|
||||
}
|
||||
|
||||
getContentHeight(): number {
|
||||
return this.view.getContentHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `aria-label` attribute of the underlying root HTML element.
|
||||
*/
|
||||
public set ariaLabel(value: string) {
|
||||
this.getHTMLElement().setAttribute('aria-label', value);
|
||||
}
|
||||
|
||||
public getHighlight(): any {
|
||||
return this.model.getHighlight();
|
||||
}
|
||||
|
||||
public clearHighlight(eventPayload?: any): void {
|
||||
this.model.setHighlight(null, eventPayload);
|
||||
}
|
||||
|
||||
public setSelection(elements: any[], eventPayload?: any): void {
|
||||
this.model.setSelection(elements, eventPayload);
|
||||
}
|
||||
|
||||
public getSelection(): any[] {
|
||||
return this.model.getSelection();
|
||||
}
|
||||
|
||||
public clearSelection(eventPayload?: any): void {
|
||||
this.model.setSelection([], eventPayload);
|
||||
}
|
||||
|
||||
public setFocus(element?: any, eventPayload?: any): void {
|
||||
this.model.setFocus(element, eventPayload);
|
||||
}
|
||||
|
||||
public getFocus(): any {
|
||||
return this.model.getFocus();
|
||||
}
|
||||
|
||||
public focusNext(count?: number, eventPayload?: any): void {
|
||||
this.model.focusNext(count, eventPayload);
|
||||
}
|
||||
|
||||
public focusPrevious(count?: number, eventPayload?: any): void {
|
||||
this.model.focusPrevious(count, eventPayload);
|
||||
}
|
||||
|
||||
public focusParent(eventPayload?: any): void {
|
||||
this.model.focusParent(eventPayload);
|
||||
}
|
||||
|
||||
public focusFirstChild(eventPayload?: any): void {
|
||||
this.model.focusFirstChild(eventPayload);
|
||||
}
|
||||
|
||||
public focusFirst(eventPayload?: any, from?: any): void {
|
||||
this.model.focusFirst(eventPayload, from);
|
||||
}
|
||||
|
||||
public focusNth(index: number, eventPayload?: any): void {
|
||||
this.model.focusNth(index, eventPayload);
|
||||
}
|
||||
|
||||
public focusLast(eventPayload?: any, from?: any): void {
|
||||
this.model.focusLast(eventPayload, from);
|
||||
}
|
||||
|
||||
public focusNextPage(eventPayload?: any): void {
|
||||
this.view.focusNextPage(eventPayload);
|
||||
}
|
||||
|
||||
public focusPreviousPage(eventPayload?: any): void {
|
||||
this.view.focusPreviousPage(eventPayload);
|
||||
}
|
||||
|
||||
public clearFocus(eventPayload?: any): void {
|
||||
this.model.setFocus(null, eventPayload);
|
||||
}
|
||||
|
||||
// @todo anthonydresser 4/12/19 we need to refactor our code to not need these methods
|
||||
public addTraits(trait: string, elements: any[]): void {
|
||||
this.model.addTraits(trait, elements);
|
||||
}
|
||||
|
||||
public removeTraits(trait: string, elements: any[]): void {
|
||||
this.model.removeTraits(trait, elements);
|
||||
}
|
||||
|
||||
public select(element: any, eventPayload?: any): void {
|
||||
this.model.select(element, eventPayload);
|
||||
}
|
||||
|
||||
public deselect(element: any, eventPayload?: any): void {
|
||||
this.model.deselect(element, eventPayload);
|
||||
}
|
||||
// END @todo
|
||||
|
||||
getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator<any> {
|
||||
return new MappedNavigator(this.model.getNavigator(fromElement, subTreeOnly), i => i && i.getElement());
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onDispose.fire();
|
||||
this.model.dispose();
|
||||
this.view.dispose();
|
||||
this._onDidChangeFocus.dispose();
|
||||
this._onDidChangeSelection.dispose();
|
||||
this._onHighlightChange.dispose();
|
||||
this._onDidExpandItem.dispose();
|
||||
this._onDidCollapseItem.dispose();
|
||||
this._onDispose.dispose();
|
||||
}
|
||||
}
|
||||
1497
src/sql/base/parts/tree/browser/treeModel.ts
Normal file
18
src/sql/base/parts/tree/browser/treeUtils.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as _ from 'sql/base/parts/tree/browser/tree';
|
||||
|
||||
export function isEqualOrParent(tree: _.ITree, element: any, candidateParent: any): boolean {
|
||||
const nav = tree.getNavigator(element);
|
||||
|
||||
do {
|
||||
if (element === candidateParent) {
|
||||
return true;
|
||||
}
|
||||
} while (element = nav.parent()); // eslint-disable-line no-cond-assign
|
||||
|
||||
return false;
|
||||
}
|
||||
1616
src/sql/base/parts/tree/browser/treeView.ts
Normal file
238
src/sql/base/parts/tree/browser/treeViewModel.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ArrayNavigator, INavigator } from 'vs/base/common/navigator';
|
||||
import { Item } from './treeModel';
|
||||
|
||||
export interface IViewItem {
|
||||
model: Item;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export class HeightMap {
|
||||
|
||||
private heightMap: IViewItem[] = [];
|
||||
private indexes: { [item: string]: number; } = {};
|
||||
|
||||
getContentHeight(): number {
|
||||
let last = this.heightMap[this.heightMap.length - 1];
|
||||
return !last ? 0 : last.top + last.height;
|
||||
}
|
||||
|
||||
onInsertItems(iterator: INavigator<Item>, afterItemId: string | null = null): number | undefined {
|
||||
let item: Item | null = null;
|
||||
let viewItem: IViewItem;
|
||||
let i: number, j: number;
|
||||
let totalSize: number;
|
||||
let sizeDiff = 0;
|
||||
|
||||
if (afterItemId === null) {
|
||||
i = 0;
|
||||
totalSize = 0;
|
||||
} else {
|
||||
i = this.indexes[afterItemId] + 1;
|
||||
viewItem = this.heightMap[i - 1];
|
||||
|
||||
if (!viewItem) {
|
||||
onUnexpectedError('view item doesn\'t exist');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
totalSize = viewItem.top + viewItem.height;
|
||||
}
|
||||
|
||||
const startingIndex = i;
|
||||
let itemsToInsert: IViewItem[] = [];
|
||||
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (item = iterator.next()) {
|
||||
viewItem = this.createViewItem(item);
|
||||
viewItem.top = totalSize + sizeDiff;
|
||||
|
||||
this.indexes[item.id] = i++;
|
||||
itemsToInsert.push(viewItem);
|
||||
sizeDiff += viewItem.height;
|
||||
}
|
||||
|
||||
this.heightMap.splice(startingIndex, 0, ...itemsToInsert);
|
||||
|
||||
for (j = i; j < this.heightMap.length; j++) {
|
||||
viewItem = this.heightMap[j];
|
||||
viewItem.top += sizeDiff;
|
||||
this.indexes[viewItem.model.id] = j;
|
||||
}
|
||||
|
||||
for (j = itemsToInsert.length - 1; j >= 0; j--) {
|
||||
this.onInsertItem(itemsToInsert[j]);
|
||||
}
|
||||
|
||||
for (j = this.heightMap.length - 1; j >= i; j--) {
|
||||
this.onRefreshItem(this.heightMap[j]);
|
||||
}
|
||||
|
||||
return sizeDiff;
|
||||
}
|
||||
|
||||
onInsertItem(item: IViewItem): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Contiguous items
|
||||
onRemoveItems(iterator: INavigator<string>): void {
|
||||
let itemId: string | null = null;
|
||||
let viewItem: IViewItem;
|
||||
let startIndex: number | null = null;
|
||||
let i = 0;
|
||||
let sizeDiff = 0;
|
||||
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (itemId = iterator.next()) {
|
||||
i = this.indexes[itemId];
|
||||
viewItem = this.heightMap[i];
|
||||
|
||||
if (!viewItem) {
|
||||
onUnexpectedError('view item doesn\'t exist');
|
||||
return;
|
||||
}
|
||||
|
||||
sizeDiff -= viewItem.height;
|
||||
delete this.indexes[itemId];
|
||||
this.onRemoveItem(viewItem);
|
||||
|
||||
if (startIndex === null) {
|
||||
startIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeDiff === 0 || startIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.heightMap.splice(startIndex, i - startIndex + 1);
|
||||
|
||||
for (i = startIndex; i < this.heightMap.length; i++) {
|
||||
viewItem = this.heightMap[i];
|
||||
viewItem.top += sizeDiff;
|
||||
this.indexes[viewItem.model.id] = i;
|
||||
this.onRefreshItem(viewItem);
|
||||
}
|
||||
}
|
||||
|
||||
onRemoveItem(item: IViewItem): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
onRefreshItemSet(items: Item[]): void {
|
||||
let sortedItems = items.sort((a, b) => this.indexes[a.id] - this.indexes[b.id]);
|
||||
this.onRefreshItems(new ArrayNavigator(sortedItems));
|
||||
}
|
||||
|
||||
// Ordered, but not necessarily contiguous items
|
||||
onRefreshItems(iterator: INavigator<Item>): void {
|
||||
let item: Item | null = null;
|
||||
let viewItem: IViewItem;
|
||||
let newHeight: number;
|
||||
let i: number, j: number | null = null;
|
||||
let cummDiff = 0;
|
||||
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (item = iterator.next()) {
|
||||
i = this.indexes[item.id];
|
||||
|
||||
for (; cummDiff !== 0 && j !== null && j < i; j++) {
|
||||
viewItem = this.heightMap[j];
|
||||
viewItem.top += cummDiff;
|
||||
this.onRefreshItem(viewItem);
|
||||
}
|
||||
|
||||
viewItem = this.heightMap[i];
|
||||
newHeight = item.getHeight();
|
||||
viewItem.top += cummDiff;
|
||||
cummDiff += newHeight - viewItem.height;
|
||||
viewItem.height = newHeight;
|
||||
this.onRefreshItem(viewItem, true);
|
||||
|
||||
j = i + 1;
|
||||
}
|
||||
|
||||
if (cummDiff !== 0 && j !== null) {
|
||||
for (; j < this.heightMap.length; j++) {
|
||||
viewItem = this.heightMap[j];
|
||||
viewItem.top += cummDiff;
|
||||
this.onRefreshItem(viewItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRefreshItem(item: IViewItem, needsRender: boolean = false): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
itemsCount(): number {
|
||||
return this.heightMap.length;
|
||||
}
|
||||
|
||||
itemAt(position: number): string {
|
||||
return this.heightMap[this.indexAt(position)].model.id;
|
||||
}
|
||||
|
||||
withItemsInRange(start: number, end: number, fn: (item: string) => void): void {
|
||||
start = this.indexAt(start);
|
||||
end = this.indexAt(end);
|
||||
for (let i = start; i <= end; i++) {
|
||||
fn(this.heightMap[i].model.id);
|
||||
}
|
||||
}
|
||||
|
||||
indexAt(position: number): number {
|
||||
let left = 0;
|
||||
let right = this.heightMap.length;
|
||||
let center: number;
|
||||
let item: IViewItem;
|
||||
|
||||
// Binary search
|
||||
while (left < right) {
|
||||
center = Math.floor((left + right) / 2);
|
||||
item = this.heightMap[center];
|
||||
|
||||
if (position < item.top) {
|
||||
right = center;
|
||||
} else if (position >= item.top + item.height) {
|
||||
if (left === center) {
|
||||
break;
|
||||
}
|
||||
left = center;
|
||||
} else {
|
||||
return center;
|
||||
}
|
||||
}
|
||||
|
||||
return this.heightMap.length;
|
||||
}
|
||||
|
||||
indexAfter(position: number): number {
|
||||
return Math.min(this.indexAt(position) + 1, this.heightMap.length);
|
||||
}
|
||||
|
||||
itemAtIndex(index: number): IViewItem {
|
||||
return this.heightMap[index];
|
||||
}
|
||||
|
||||
itemAfter(item: IViewItem): IViewItem {
|
||||
return this.heightMap[this.indexes[item.model.id] + 1] || null;
|
||||
}
|
||||
|
||||
protected createViewItem(item: Item): IViewItem {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.heightMap = [];
|
||||
this.indexes = {};
|
||||
}
|
||||
}
|
||||
1662
src/sql/base/parts/tree/test/browser/treeModel.test.ts
Normal file
252
src/sql/base/parts/tree/test/browser/treeViewModel.test.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ArrayNavigator } from 'vs/base/common/navigator';
|
||||
import { HeightMap, IViewItem } from 'sql/base/parts/tree/browser/treeViewModel';
|
||||
|
||||
function makeItem(id: any, height: any): any {
|
||||
return {
|
||||
id: id,
|
||||
getHeight: function () { return height; },
|
||||
isExpanded: function () { return false; },
|
||||
getAllTraits: () => []
|
||||
};
|
||||
}
|
||||
|
||||
function makeItems(...args: any[]) {
|
||||
let r: any[] = [];
|
||||
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
r.push(makeItem(args[i], args[i + 1]));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function makeNavigator(...args: any[]): any {
|
||||
let items = makeItems.apply(null, args);
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: function () {
|
||||
return items[i++] || null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class TestHeightMap extends HeightMap {
|
||||
|
||||
protected override createViewItem(item: any): IViewItem {
|
||||
return {
|
||||
model: item,
|
||||
top: 0,
|
||||
height: item.getHeight(),
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
suite('TreeView - HeightMap', () => {
|
||||
let rangeMap: HeightMap;
|
||||
|
||||
setup(() => {
|
||||
rangeMap = new TestHeightMap();
|
||||
rangeMap.onInsertItems(makeNavigator('a', 3, 'b', 30, 'c', 25, 'd', 2));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
rangeMap.dispose();
|
||||
rangeMap = null!;
|
||||
});
|
||||
|
||||
test('simple', () => {
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(33), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(40), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(57), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(58), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(59), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(60));
|
||||
});
|
||||
|
||||
test('onInsertItems at beginning', () => {
|
||||
let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8);
|
||||
rangeMap.onInsertItems(navigator);
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(4), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(23), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(24), 'z');
|
||||
assert.strictEqual(rangeMap.itemAt(31), 'z');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(34), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(35), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(64), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(65), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(89), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(90), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(91), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(92));
|
||||
});
|
||||
|
||||
test('onInsertItems in middle', () => {
|
||||
let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8);
|
||||
rangeMap.onInsertItems(navigator, 'a');
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(6), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(7), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(26), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(27), 'z');
|
||||
assert.strictEqual(rangeMap.itemAt(34), 'z');
|
||||
assert.strictEqual(rangeMap.itemAt(35), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(64), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(65), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(89), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(90), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(91), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(92));
|
||||
});
|
||||
|
||||
test('onInsertItems at end', () => {
|
||||
let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8);
|
||||
rangeMap.onInsertItems(navigator, 'd');
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(33), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(57), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(58), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(59), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(60), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(63), 'x');
|
||||
assert.strictEqual(rangeMap.itemAt(64), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(83), 'y');
|
||||
assert.strictEqual(rangeMap.itemAt(84), 'z');
|
||||
assert.strictEqual(rangeMap.itemAt(91), 'z');
|
||||
assert.throws(() => rangeMap.itemAt(92));
|
||||
});
|
||||
|
||||
test('onRemoveItems at beginning', () => {
|
||||
rangeMap.onRemoveItems(new ArrayNavigator(['a', 'b']));
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(24), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(25), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(26), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(27));
|
||||
});
|
||||
|
||||
test('onRemoveItems in middle', () => {
|
||||
rangeMap.onRemoveItems(new ArrayNavigator(['c']));
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(33), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(34), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(35));
|
||||
});
|
||||
|
||||
test('onRemoveItems at end', () => {
|
||||
rangeMap.onRemoveItems(new ArrayNavigator(['c', 'd']));
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'b');
|
||||
assert.throws(() => rangeMap.itemAt(33));
|
||||
});
|
||||
|
||||
test('onRefreshItems at beginning', () => {
|
||||
let navigator = makeNavigator('a', 1, 'b', 1);
|
||||
rangeMap.onRefreshItems(navigator);
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(1), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(26), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(27), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(28), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(29));
|
||||
});
|
||||
|
||||
test('onRefreshItems in middle', () => {
|
||||
let navigator = makeNavigator('b', 40, 'c', 4);
|
||||
rangeMap.onRefreshItems(navigator);
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(42), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(43), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(46), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(47), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(48), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(49));
|
||||
});
|
||||
|
||||
test('onRefreshItems at end', () => {
|
||||
let navigator = makeNavigator('d', 22);
|
||||
rangeMap.onRefreshItems(navigator);
|
||||
|
||||
assert.strictEqual(rangeMap.itemAt(0), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(2), 'a');
|
||||
assert.strictEqual(rangeMap.itemAt(3), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(32), 'b');
|
||||
assert.strictEqual(rangeMap.itemAt(33), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(57), 'c');
|
||||
assert.strictEqual(rangeMap.itemAt(58), 'd');
|
||||
assert.strictEqual(rangeMap.itemAt(79), 'd');
|
||||
assert.throws(() => rangeMap.itemAt(80));
|
||||
});
|
||||
|
||||
test('withItemsInRange', () => {
|
||||
let i = 0;
|
||||
let itemsInRange = ['a', 'b'];
|
||||
rangeMap.withItemsInRange(2, 27, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['a', 'b'];
|
||||
rangeMap.withItemsInRange(0, 3, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['a'];
|
||||
rangeMap.withItemsInRange(0, 2, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['a'];
|
||||
rangeMap.withItemsInRange(0, 2, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['b', 'c'];
|
||||
rangeMap.withItemsInRange(15, 39, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['a', 'b', 'c', 'd'];
|
||||
rangeMap.withItemsInRange(1, 58, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
|
||||
i = 0;
|
||||
itemsInRange = ['c', 'd'];
|
||||
rangeMap.withItemsInRange(45, 58, function (item) { assert.strictEqual(item, itemsInRange[i++]); });
|
||||
assert.strictEqual(i, itemsInRange.length);
|
||||
});
|
||||
});
|
||||