UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

173 lines 9.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-deprecated */ Object.defineProperty(exports, "__esModule", { value: true }); exports.reloadTree = reloadTree; const concat_1 = require("rxjs/internal/observable/concat"); const defer_1 = require("rxjs/internal/observable/defer"); const empty_1 = require("rxjs/internal/observable/empty"); const from_1 = require("rxjs/internal/observable/from"); const concatMap_1 = require("rxjs/internal/operators/concatMap"); const endWith_1 = require("rxjs/internal/operators/endWith"); const expand_1 = require("rxjs/internal/operators/expand"); const filter_1 = require("rxjs/internal/operators/filter"); const ignoreElements_1 = require("rxjs/internal/operators/ignoreElements"); const map_1 = require("rxjs/internal/operators/map"); const mergeMap_1 = require("rxjs/internal/operators/mergeMap"); const take_1 = require("rxjs/internal/operators/take"); const tap_1 = require("rxjs/internal/operators/tap"); const components_react_1 = require("@itwin/components-react"); const core_bentley_1 = require("@itwin/core-bentley"); const Utils_js_1 = require("../Utils.js"); /** * Creates a new tree model from scratch while attempting to match provided tree model's expanded structure. * @param treeModel Previous tree model. * @param dataProvider Tree node provider. * @param pageSize Data provider's page size. * @param itemsRange Range describing rendered items that are visible. * @returns An observable which will emit a new [TreeModelSource]($components-react) and complete. * @internal */ function reloadTree(treeModel, dataProvider, pageSize, itemsRange) { const modelSource = new components_react_1.TreeModelSource(); const nodeLoader = new TreeReloader(dataProvider, modelSource, pageSize, treeModel, itemsRange); return nodeLoader.reloadTree().pipe((0, endWith_1.endWith)(modelSource)); } class TreeReloader extends components_react_1.PagedTreeNodeLoader { previousTreeModel; itemsRange; constructor(dataProvider, modelSource, pageSize, previousTreeModel, itemsRange) { super(dataProvider, modelSource, pageSize); this.previousTreeModel = previousTreeModel; this.itemsRange = itemsRange; } reloadTree() { const previouslyExpandedNodes = collectExpandedNodes(undefined, this.previousTreeModel); return (0, concat_1.concat)( // We need to know root node count before continuing this.loadNode(this.modelSource.getModel().getRootNode(), 0), this.reloadPreviouslyExpandedNodes(previouslyExpandedNodes), this.reloadVisibleNodes()).pipe((0, ignoreElements_1.ignoreElements)()); } reloadPreviouslyExpandedNodes(previouslyExpandedNodes) { return (0, from_1.from)(previouslyExpandedNodes).pipe( // Process expanded nodes recursively, breadth first (0, expand_1.expand)((expandedNode) => { const node = this.modelSource.getModel().getNode(expandedNode.id); if (node !== undefined) { // The expanded node is already loaded in the new tree model, now load and expand its children recursively return (0, concat_1.concat)(this.loadChildren(node), expandedNode.expandedChildren); } // The expanded node is either not loaded yet, or does not exist in the new tree hierarchy const parentNode = getTreeNode(this.modelSource.getModel(), expandedNode.parentId); if (parentNode === undefined || parentNode.numChildren === undefined) { // Cannot determine sibling count. Assume parent is missing from the new tree or something went wrong. return empty_1.EMPTY; } if (parentNode.numChildren === 0) { // Parent node no longer has any children, thus we will not find the expanded node return empty_1.EMPTY; } // Try to make the expanded node appear in the new tree hierarchy. Test three locations: at, a page before, // and a page after previous known location. // TODO: We should keep a list of nodes that we failed to find. There is a chance that we will load them // accidentally while searching for other expanded nodes under the same parent. return (0, from_1.from)([ Math.min(expandedNode.index, parentNode.numChildren - 1), Math.min(Math.max(0, expandedNode.index - this.pageSize), parentNode.numChildren - 1), Math.min(expandedNode.index + this.pageSize, parentNode.numChildren - 1), ]).pipe( // For each guess, load the corresponding page (0, concatMap_1.concatMap)((index) => this.loadNode(parentNode, index)), // Stop making guesses when the node is found (0, map_1.map)(() => this.modelSource.getModel().getNode(expandedNode.id)), (0, filter_1.filter)((loadedNode) => loadedNode !== undefined), (0, take_1.take)(1), // If the node is found, load and expand its children recursively (0, concatMap_1.concatMap)((loadedNode) => { (0, core_bentley_1.assert)(loadedNode !== undefined); return (0, concat_1.concat)(this.loadChildren(loadedNode), expandedNode.expandedChildren); })); })); } reloadVisibleNodes() { return (0, defer_1.defer)(() => { // if visible range is not provided do not load any more nodes if (!this.itemsRange) { return empty_1.EMPTY; } // collect not loaded (placeholder) nodes that are in visible range const visibleNodes = (0, components_react_1.computeVisibleNodes)(this.modelSource.getModel()); const visibleRange = getVisibleRange(this.itemsRange, visibleNodes); const notLoadedNode = []; for (let i = visibleRange.start; i <= visibleRange.end; i++) { const node = visibleNodes.getAtIndex(i); if (!node || !(0, components_react_1.isTreeModelNodePlaceholder)(node)) { continue; } notLoadedNode.push(node); } // load all placeholder nodes in visible range return (0, from_1.from)(notLoadedNode).pipe((0, mergeMap_1.mergeMap)((placeholder) => { const parentNode = placeholder.parentId ? this.modelSource.getModel().getNode(placeholder.parentId) : this.modelSource.getModel().getRootNode(); (0, core_bentley_1.assert)(parentNode !== undefined); return (0, Utils_js_1.toRxjsObservable)(super.loadNode(parentNode, placeholder.childIndex)); })); }); } loadChildren(parentNode) { // If child count is known, children are already loaded, but we still need to make sure the parent node is expanded const sourceObservable = parentNode.numChildren === undefined ? this.loadNode(parentNode, 0) : empty_1.EMPTY; // Load the first page and expand the parent node return sourceObservable.pipe((0, ignoreElements_1.ignoreElements)(), (0, tap_1.tap)({ // If node loading succeeded, set parent's expansion state to `true` complete: () => this.modelSource.modifyModel((model) => { const node = model.getNode(parentNode.id); (0, core_bentley_1.assert)(node !== undefined); if ((node.numChildren ?? 0) > 0) { node.isExpanded = true; } }), })); } /** Only loads the node if it is not present in the tree model already */ loadNode(parent, childIndex) { const node = this.modelSource.getModel().getNode(parent.id, childIndex); if ((0, components_react_1.isTreeModelNode)(node)) { return empty_1.EMPTY; } return (0, Utils_js_1.toRxjsObservable)(super.loadNode(parent, childIndex)); } } function collectExpandedNodes(rootNodeId, treeModel) { const expandedNodes = []; for (const [nodeId] of treeModel.getChildren(rootNodeId)?.iterateValues() ?? []) { const node = treeModel.getNode(nodeId); if ((0, components_react_1.isTreeModelNode)(node) && node.isExpanded) { const index = treeModel.getChildOffset(node.parentId, node.id); (0, core_bentley_1.assert)(index !== undefined); expandedNodes.push({ id: node.id, parentId: node.parentId, index, expandedChildren: collectExpandedNodes(node.id, treeModel), }); } } return expandedNodes; } function getTreeNode(treeModel, nodeId) { return nodeId === undefined ? treeModel.getRootNode() : treeModel.getNode(nodeId); } function getVisibleRange(itemsRange, visibleNodes) { if (itemsRange.visibleStopIndex < visibleNodes.getNumNodes()) { return { start: itemsRange.visibleStartIndex, end: itemsRange.visibleStopIndex }; } const visibleNodesCount = itemsRange.visibleStopIndex - itemsRange.visibleStartIndex; const endPosition = visibleNodes.getNumNodes() - 1; const startPosition = endPosition - visibleNodesCount; return { start: startPosition < 0 ? 0 : startPosition, end: endPosition < 0 ? 0 : endPosition, }; } //# sourceMappingURL=TreeReloader.js.map