packageDocumentation module import { immerable } from "immer" ; import _ from "lodash" ; import { assert } from "@itwin/core-bentley" ; import { PropertyRecord } from "@itwin/appui-abstract" ; import { CheckBoxState } from "@itwin/core-react" ; import { DelayLoadedTreeNodeItem , ImmediatelyLoadedTreeNodeItem , TreeNodeItem } from "../TreeDataProvider" ; import { SparseArray , SparseTree } from "./internal/SparseTree" ; public export interface TreeModelNode { readonly id : string ; readonly parentId : string | undefined ; readonly depth : number ; readonly isLoading ? : boolean ; readonly numChildren : number | undefined ; readonly description : string | undefined ; readonly isExpanded : boolean ; readonly label : PropertyRecord ; readonly isSelected : boolean ; /** Specifies that node is in editing mode. It holds callbacks that are used by node editor. */ readonly editingInfo ? : TreeModelNodeEditingInfo ; readonly checkbox : CheckBoxInfo ; readonly item : TreeNodeItem ; } public export interface CheckBoxInfo { readonly state : CheckBoxState ; readonly tooltip ? : string ; readonly isDisabled : boolean ; readonly isVisible : boolean ; } public export interface MutableTreeModelNode extends TreeModelNode { isLoading : boolean ; description : string ; isExpanded : boolean ; label : PropertyRecord ; isSelected : boolean ; /** Specifies that node is in editing mode. It holds callbacks that are used by node editor. */ editingInfo ? : TreeModelNodeEditingInfo ; checkbox : MutableCheckBoxInfo ; item : TreeNodeItem ; } public export interface TreeModelNodeEditingInfo { onCommit : ( node : TreeModelNode , newValue : string ) => void ; onCancel : ( ) => void ; } public export interface MutableCheckBoxInfo extends CheckBoxInfo { state : CheckBoxState ; tooltip ? : string ; isDisabled : boolean ; isVisible : boolean ; } public export interface TreeModelNodePlaceholder { readonly childIndex : number ; readonly depth : number ; readonly parentId ? : string ; } public export interface TreeModelRootNode { readonly depth : - 1 ; readonly id : undefined ; readonly numChildren : number | undefined ; } public export interface TreeModelNodeInput { readonly description ? : string ; readonly isExpanded : boolean ; readonly id : string ; readonly item : TreeNodeItem ; readonly label : PropertyRecord ; readonly isLoading : boolean ; readonly numChildren ? : number ; readonly isSelected : boolean ; } public export type TreeModelNodeType = TreeModelNode | TreeModelNodePlaceholder | TreeModelRootNode ; public export function isTreeModelNode ( obj ) { return obj !== undefined && ! isTreeModelNodePlaceholder ( obj ) && ! isTreeModelRootNode ( obj ) ; } public export function isTreeModelNodePlaceholder ( obj ) { return obj !== undefined && "childIndex" in obj ; } public export function isTreeModelRootNode ( obj ) { return obj !== undefined && ( obj as TreeModelRootNode ) . id === undefined && ! ( "childIndex" in obj ) ; } public export type TreeNodeItemData = ImmediatelyLoadedTreeNodeItem & DelayLoadedTreeNodeItem ; public export interface VisibleTreeNodes extends Iterable < TreeModelNode | TreeModelNodePlaceholder > { getNumNodes ( ) : number ; getAtIndex ( index ) : TreeModelNode | TreeModelNodePlaceholder | undefined ; getModel ( ) : TreeModel ; getNumRootNodes ( ) : number | undefined ; getIndexOfNode ( nodeId ) : number ; } public export interface TreeModel { getRootNode ( ) : TreeModelRootNode ; getNode ( id ) : TreeModelNode | undefined ; getNode ( parentId , childIndex ) : TreeModelNode | TreeModelNodePlaceholder | undefined ; getNode ( nodeId , childIndex ? ) : TreeModelNode | TreeModelNodePlaceholder | TreeModelRootNode | undefined ; getChildren ( parentId ) : SparseArray < string > | undefined ; getChildOffset ( parentId , childId ) : number | undefined ; iterateTreeModelNodes ( parentId ? ) : IterableIterator < TreeModelNode > ; } public export class MutableTreeModel implements TreeModel { public [ immerable ] = true ; private _tree = new SparseTree < MutableTreeModelNode > ( ) ; private _rootNode : TreeModelRootNode = { depth : - 1 , id : undefined , numChildren : undefined } ; /** Returns root node of a tree. This node is not visible and is there to allow having multiple visible root nodes. */ public getRootNode ( ) { return this . _rootNode ; } returns public getNode ( id ) ; public getNode ( parentId , childIndex ) ; public getNode ( nodeId , childIndex ? ) { if ( childIndex === undefined ) { return this . _tree . getNode ( nodeId ! ) ; } const children = this . _tree . getChildren ( nodeId ) ; const childId = children !== undefined ? children . get ( childIndex ) : undefined ; if ( childId !== undefined ) { return this . _tree . getNode ( childId ) ; } const parentNode = nodeId === undefined ? this . _rootNode : this . _tree . getNode ( nodeId ) ; if ( parentNode !== undefined ) { return { childIndex , depth : parentNode . depth + 1 , parentId : nodeId } ; } return undefined ; } param parentId returns public getChildren ( parentId ) { return this . _tree . getChildren ( parentId ) ; } /** Returns children offset in children array for specific parent. */ public getChildOffset ( parentId , childId ) { return this . _tree . getChildOffset ( parentId , childId ) ; } /**
     * Sets children for parent node starting from the specific offset.
     * If offset overlaps with already added nodes, the overlapping nodes are overwritten.
     */ public setChildren ( parentId , nodeInputs , offset ) { const parentNode = parentId === undefined ? this . _rootNode : this . _tree . getNode ( parentId ) ; if ( parentNode === undefined ) return ; const children = [ ] ; for ( const input of nodeInputs ) { const child = MutableTreeModel . createTreeModelNode ( parentNode , input ) ; children . push ( child ) ; } this . _tree . setChildren ( parentNode . id , children , offset ) ; MutableTreeModel . setNumChildrenForNode ( parentNode , this . _tree . getChildren ( parentNode . id ) ) ; } /**
     * Inserts child in the specified position.
     * If offset is higher then current length of children array, the length is increased.
     */ public insertChild ( parentId , childNodeInput , offset ) { const parentNode = parentId === undefined ? this . _rootNode : this . _tree . getNode ( parentId ) ; if ( parentNode === undefined ) return ; const child = MutableTreeModel . createTreeModelNode ( parentNode , childNodeInput ) ; this . _tree . insertChild ( parentNode . id , child , offset ) ; MutableTreeModel . setNumChildrenForNode ( parentNode , this . _tree . getChildren ( parentNode . id ) ) ; } returns public changeNodeId ( currentId , newId ) { const node = this . getNode ( currentId ) ; if ( node === undefined ) { return false ; } if ( currentId === newId ) { return true ; } const index = this . getChildOffset ( node . parentId , currentId ) ; assert ( index !== undefined ) ; if ( ! this . _tree . setNodeId ( node . parentId , index , newId ) ) { return false ; } ( node . id as string ) = newId ; for ( const [ childId ] of this . getChildren ( newId ) ?. iterateValues ( ) ?? [ ] ) { const child = this . getNode ( childId ) ; assert ( child !== undefined ) ; ( child . parentId as string ) = newId ; } return true ; } param sourceNodeId param targetParentId param targetIndex returns public moveNode ( sourceNodeId , targetParentId , targetIndex ) { const sourceNode = this . getNode ( sourceNodeId ) ; if ( sourceNode === undefined ) { return false ; } const targetParent = targetParentId === undefined ? this . _rootNode : this . getNode ( targetParentId ) ; if ( targetParent === undefined || targetParent . numChildren === undefined ) { return false ; } if ( targetParentId !== undefined && this . areNodesRelated ( sourceNodeId , targetParentId ) ) { return false ; } this . _tree . moveNode ( sourceNode . parentId , sourceNodeId , targetParentId , targetIndex ) ; const sourceParent = sourceNode . parentId === undefined ? this . _rootNode : this . getNode ( sourceNode . parentId ) ; assert ( sourceParent !== undefined ) ; MutableTreeModel . setNumChildrenForNode ( sourceParent , this . _tree . getChildren ( sourceNode . parentId ) ) ; MutableTreeModel . setNumChildrenForNode ( targetParent , this . _tree . getChildren ( targetParentId ) ) ; ( sourceNode . parentId as string | undefined ) = targetParentId ; const updateDepths = ( parentId , depth ) => { const node = this . getNode ( parentId ) ; assert ( node !== undefined ) ; ( node . depth as number ) = depth ; for ( const [ nodeId ] of this . getChildren ( parentId ) ?. iterateValues ( ) ?? [ ] ) { updateDepths ( nodeId , depth + 1 ) ; } } ; updateDepths ( sourceNodeId , targetParent . depth + 1 ) ; return true ; } private areNodesRelated ( ancestorNodeId , descendantNodeId ) { const node = this . getNode ( descendantNodeId ) ; if ( node === undefined || node . parentId === undefined ) { return false ; } return node . parentId === ancestorNodeId ? true : this . areNodesRelated ( ancestorNodeId , node . parentId ) ; } /**
     * Sets the number of child nodes a parent is expected to contain. All child nodes of this parent will be subsequently
     * removed.
     */ public setNumChildren ( parentId , numChildren ) { const parentNode = parentId === undefined ? this . _rootNode : this . _tree . getNode ( parentId ) ; if ( parentNode === undefined ) { return ; } ( parentNode . numChildren as number | undefined ) = numChildren ; this . _tree . setNumChildren ( parentId , numChildren ?? 0 ) ; } param parentId param child public removeChild ( parentId , child ) { const parentNode = parentId === undefined ? this . _rootNode : this . _tree . getNode ( parentId ) ; this . _tree . removeChild ( parentId , child ) ; if ( parentNode ) MutableTreeModel . setNumChildrenForNode ( parentNode , this . _tree . getChildren ( parentNode . id ) ) ; } param parentId public clearChildren ( parentId ) { const parentNode = parentId === undefined ? this . _rootNode : this . _tree . getNode ( parentId ) ; if ( parentNode !== undefined ) { ( parentNode . numChildren as number | undefined ) = undefined ; } this . _tree . deleteSubtree ( parentId , false ) ; } /** Iterates over all nodes present in the tree model. */ public * iterateTreeModelNodes ( parentId ? ) { const _this = this ; function * iterateDescendants ( subParentId ) { const children = _this . getChildren ( subParentId ) ; if ( children === undefined ) { return ; } for ( const [ nodeId ] of children . iterateValues ( ) ) { const node = _this . getNode ( nodeId ) ; if ( node !== undefined ) { yield node ; yield * iterateDescendants ( nodeId ) ; } } } yield * iterateDescendants ( parentId ) ; } private static setNumChildrenForNode ( node , children ) { const numChildren = children ? children . getLength ( ) : undefined ; if ( node . numChildren === numChildren ) return ; ( node . numChildren as number | undefined ) = numChildren ; } private static createTreeModelNode ( parentNode , input ) { return { id : input . id , parentId : parentNode . id , depth : parentNode . depth + 1 , isLoading : input . isLoading , numChildren : input . numChildren , description : input . description || "" , isExpanded : input . isExpanded , label : input . label , isSelected : input . isSelected , checkbox : { state : input . item . checkBoxState || CheckBoxState . Off , isDisabled : ! ! input . item . isCheckboxDisabled , isVisible : ! ! input . item . isCheckboxVisible } , item : _ . cloneDeep ( input . item ) } ; } } public export function computeVisibleNodes ( model ) { const result = getVisibleDescendants ( model , model . getRootNode ( ) ) ; return { getNumNodes : ( ) => result . length , getAtIndex : ( index ) : TreeModelNode | TreeModelNodePlaceholder | undefined => { return result [ index ] ; } , getModel : ( ) => model , getNumRootNodes : ( ) => model . getRootNode ( ) . numChildren , [ Symbol . iterator ] : ( ) => result [ Symbol . iterator ] ( ) , getIndexOfNode : ( nodeId ) : number => { return result . findIndex ( ( visibleNode ) => ( visibleNode as TreeModelNode ) . id === nodeId ) ; } } ; } internal export function getVisibleDescendants ( model , parentNode , result = [ ] ) { const children = model . getChildren ( parentNode . id ) ; if ( ! children ) { return result ; } let index = 0 ; for ( const childId of children ) { if ( childId === undefined ) { result . push ( { parentId : parentNode . id , depth : parentNode . depth + 1 , childIndex : index } ) ; } else { const childNode = model . getNode ( childId ) ; if ( childNode === undefined ) { result . push ( { parentId : parentNode . id , depth : parentNode . depth + 1 , childIndex : index } ) ; } else { result . push ( childNode ) ; if ( childNode . isExpanded && childNode . numChildren !== undefined ) { getVisibleDescendants ( model , childNode , result ) ; } } } ++ index ; } return result ; }