@itwin/presentation-components
Version:
React components based on iTwin.js Presentation library
173 lines • 9.7 kB
JavaScript
"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