@itwin/presentation-hierarchies-react
Version:
React components based on `@itwin/presentation-hierarchies`
311 lines • 13.5 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.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeActions = void 0;
require("./DisposePolyfill.js");
const immer_1 = require("immer");
const rxjs_1 = require("rxjs");
const presentation_hierarchies_1 = require("@itwin/presentation-hierarchies");
const TreeLoader_js_1 = require("./TreeLoader.js");
const TreeModel_js_1 = require("./TreeModel.js");
const Utils_js_1 = require("./Utils.js");
(0, immer_1.enableMapSet)();
/** @internal */
class TreeActions {
_onModelChanged;
_onLoad;
_onHierarchyLimitExceeded;
_onHierarchyLoadError;
_loader;
_nodeIdFactory;
_currentModel;
_reset = new rxjs_1.Subject();
_nodeLoader;
constructor(_onModelChanged, _onLoad, _onHierarchyLimitExceeded, _onHierarchyLoadError, nodeIdFactory, seed) {
this._onModelChanged = _onModelChanged;
this._onLoad = _onLoad;
this._onHierarchyLimitExceeded = _onHierarchyLimitExceeded;
this._onHierarchyLoadError = _onHierarchyLoadError;
this._loader = new NoopTreeLoader();
this._nodeIdFactory = nodeIdFactory ?? Utils_js_1.createNodeId;
this._currentModel = seed ?? /* c8 ignore next */ {
idToNode: new Map(),
parentChildMap: new Map(),
rootNode: { id: undefined, nodeData: undefined },
};
this._nodeLoader = this.createNodeLoader();
}
createNodeLoader() {
const subject = new rxjs_1.Subject();
subject
.pipe((0, rxjs_1.groupBy)((item) => item.parentId), (0, rxjs_1.mergeMap)((group) => group.pipe((0, rxjs_1.buffer)(group.pipe((0, rxjs_1.debounceTime)(0))), (0, rxjs_1.map)((groupArr) => {
let returnIndex = groupArr.length - 1;
groupArr.forEach((member, index) => {
if (member.discardState) {
returnIndex = index;
}
});
groupArr.forEach((member, index) => {
if (index === returnIndex) {
return;
}
member.timeTracker?.[Symbol.dispose];
member.onComplete();
});
return groupArr[returnIndex];
}), (0, rxjs_1.switchMap)((props) => this._loader.loadNodes(props.loadOptions).pipe(collectTreePartsUntil(this._reset, props.initialRootNode), (0, rxjs_1.tap)({
next: (newModel) => {
const childNodes = newModel.parentChildMap.get(props.parentId);
const firstChildNode = childNodes?.length ? newModel.idToNode.get(childNodes[0]) : undefined;
this.handleLoadedHierarchy(props.parentId, newModel);
// only report load duration if no error occurs
if (!(firstChildNode && (0, TreeModel_js_1.isTreeModelInfoNode)(firstChildNode))) {
props.timeTracker?.finish();
}
},
complete: () => {
this.onLoadingComplete(props.parentId);
props.timeTracker?.[Symbol.dispose]();
props.onComplete();
},
}))))))
.subscribe();
return subject;
}
updateTreeModel(updater) {
const newModel = (0, immer_1.produce)(this._currentModel, updater);
if (this._currentModel === newModel) {
return;
}
this._currentModel = newModel;
this._onModelChanged(this._currentModel);
}
handleLoadedHierarchy(parentId, loadedHierarchy) {
this.updateTreeModel((model) => {
TreeModel_js_1.TreeModel.addHierarchyPart(model, parentId, loadedHierarchy);
});
}
onLoadingComplete(parentId) {
this.updateTreeModel((model) => {
TreeModel_js_1.TreeModel.setIsLoading(model, parentId, false);
});
}
getLoadAction(parentId) {
return this._currentModel.idToNode.size === 0 ? "initial-load" : parentId === undefined ? "reload" : "hierarchy-level-load";
}
loadSubTree(options, initialRootNode, discardState) {
const loadAction = this.getLoadAction(options.parent.id);
const timeTracker = new TimeTracker((time) => this._onLoad(loadAction, time));
return {
complete: new Promise((resolve) => {
this._nodeLoader.next({ loadOptions: options, onComplete: resolve, timeTracker, parentId: options.parent.id, initialRootNode, discardState });
}),
};
}
loadNodes(parentId, ignoreCache) {
const parentNode = this._currentModel.idToNode.get(parentId);
/* c8 ignore next 3 */
if (!parentNode || !(0, TreeModel_js_1.isTreeModelHierarchyNode)(parentNode)) {
return { complete: Promise.resolve() };
}
return this.loadSubTree({
parent: parentNode,
getHierarchyLevelOptions: (node) => createHierarchyLevelOptions(this._currentModel, getNonGroupedParentId(node, this._nodeIdFactory)),
shouldLoadChildren: (node) => !!node.nodeData.autoExpand,
ignoreCache,
});
}
reloadSubTree(parentId, oldModel, options) {
const currModel = this._currentModel;
const expandedNodes = !!options?.discardState ? [] : collectNodes(parentId, oldModel, (node) => node.isExpanded === true);
const collapsedNodes = !!options?.discardState ? [] : collectNodes(parentId, oldModel, (node) => node.isExpanded === false);
const getHierarchyLevelOptions = (node) => {
if (!!options?.discardState) {
return { instanceFilter: undefined, hierarchyLevelSizeLimit: undefined };
}
const filteredNodeId = getNonGroupedParentId(node, this._nodeIdFactory);
return createHierarchyLevelOptions(filteredNodeId === parentId ? currModel : oldModel, filteredNodeId);
};
const shouldLoadChildren = (node) => {
if (expandedNodes.findIndex((expandedNode) => (0, Utils_js_1.sameNodes)(expandedNode.nodeData, node.nodeData)) !== -1) {
return true;
}
if (collapsedNodes.findIndex((collapsedNode) => (0, Utils_js_1.sameNodes)(collapsedNode.nodeData, node.nodeData)) !== -1) {
return false;
}
return !!node.nodeData.autoExpand;
};
const buildNode = (node) => (!!options?.discardState || node.id === parentId ? node : addAttributes(node, oldModel));
const rootNode = parentId !== undefined ? this.getNode(parentId) : currModel.rootNode;
/* c8 ignore next 3 */
if (!rootNode || (0, TreeModel_js_1.isTreeModelInfoNode)(rootNode)) {
return { complete: Promise.resolve() };
}
if (parentId === undefined) {
// cancel all ongoing requests
this._reset.next();
}
return this.loadSubTree({ parent: rootNode, getHierarchyLevelOptions, shouldLoadChildren, buildNode, ignoreCache: options?.ignoreCache }, !!options?.discardState ? undefined : { ...currModel.rootNode }, options?.discardState);
}
reset() {
this._reset.next();
}
setHierarchyProvider(provider) {
this._loader = provider
? new TreeLoader_js_1.TreeLoader(provider, this._onHierarchyLimitExceeded, ({ parentId, type, error }) => {
if (type === "timeout") {
const loadAction = this.getLoadAction(parentId);
this._onLoad(loadAction, Number.MAX_SAFE_INTEGER);
}
this._onHierarchyLoadError({ parentId, type, error });
}, this._nodeIdFactory)
: /* c8 ignore next */ new NoopTreeLoader();
}
getNode(nodeId) {
return TreeModel_js_1.TreeModel.getNode(this._currentModel, nodeId);
}
selectNodes(nodeIds, changeType) {
this.updateTreeModel((model) => {
TreeModel_js_1.TreeModel.selectNodes(model, nodeIds, changeType);
});
}
expandNode(nodeId, isExpanded) {
let childrenAction = "none";
this.updateTreeModel((model) => {
childrenAction = TreeModel_js_1.TreeModel.expandNode(model, nodeId, isExpanded);
});
if (childrenAction === "none") {
return { complete: Promise.resolve() };
}
return this.loadNodes(nodeId, childrenAction === "reloadChildren");
}
setHierarchyLimit(nodeId, limit) {
const oldModel = this._currentModel;
let loadChildren = false;
this.updateTreeModel((model) => {
loadChildren = TreeModel_js_1.TreeModel.setHierarchyLimit(model, nodeId, limit);
});
if (!loadChildren) {
return { complete: Promise.resolve() };
}
return this.reloadSubTree(nodeId, oldModel);
}
setInstanceFilter(nodeId, filter) {
const oldModel = this._currentModel;
let loadChildren = false;
this.updateTreeModel((model) => {
loadChildren = TreeModel_js_1.TreeModel.setInstanceFilter(model, nodeId, filter);
});
if (!loadChildren) {
return { complete: Promise.resolve() };
}
return this.reloadSubTree(nodeId, oldModel);
}
reloadTree(options) {
const oldModel = this._currentModel;
this.updateTreeModel((model) => {
TreeModel_js_1.TreeModel.setIsLoading(model, options?.parentNodeId, true);
if (options?.state === "reset") {
TreeModel_js_1.TreeModel.removeSubTree(model, options?.parentNodeId);
}
});
const discardState = options?.state === "discard" || options?.state === "reset";
const ignoreCache = options?.state === "reset";
return this.reloadSubTree(options?.parentNodeId, oldModel, { discardState, ignoreCache });
}
}
exports.TreeActions = TreeActions;
function collectTreePartsUntil(untilNotifier, rootNode) {
return (source) => source.pipe((0, rxjs_1.reduce)((treeModel, loadedPart) => {
addNodesToModel(treeModel, loadedPart);
return treeModel;
}, {
idToNode: new Map(),
parentChildMap: new Map(),
rootNode: rootNode ?? { id: undefined, nodeData: undefined },
}), (0, rxjs_1.takeUntil)(untilNotifier));
}
function addNodesToModel(model, hierarchyPart) {
model.parentChildMap.set(hierarchyPart.parentId, hierarchyPart.loadedNodes.map((node) => node.id));
for (const node of hierarchyPart.loadedNodes) {
model.idToNode.set(node.id, node);
}
const parentNode = hierarchyPart.parentId ? model.idToNode.get(hierarchyPart.parentId) : undefined;
if (parentNode && (0, TreeModel_js_1.isTreeModelHierarchyNode)(parentNode)) {
parentNode.isExpanded = true;
}
}
function addAttributes(node, oldModel) {
const oldNode = oldModel.idToNode.get(node.id);
if (oldNode && (0, TreeModel_js_1.isTreeModelHierarchyNode)(oldNode)) {
node.hierarchyLimit = oldNode.hierarchyLimit;
node.instanceFilter = oldNode.instanceFilter;
node.isSelected = oldNode.isSelected;
}
return node;
}
function collectNodes(parentId, model, pred) {
const currentChildren = model.parentChildMap.get(parentId);
if (!currentChildren) {
return [];
}
if (parentId === undefined) {
return currentChildren.flatMap((child) => collectNodes(child, model, pred));
}
const currNode = model.idToNode.get(parentId);
if (!currNode || !(0, TreeModel_js_1.isTreeModelHierarchyNode)(currNode) || !pred(currNode)) {
return [];
}
return [currNode, ...currentChildren.flatMap((child) => collectNodes(child, model, pred))];
}
function getNonGroupedParentId(node, nodeIdFactory) {
if (!node.nodeData || !presentation_hierarchies_1.HierarchyNode.isGroupingNode(node.nodeData)) {
return node.id;
}
if (!node.nodeData.nonGroupingAncestor) {
return undefined;
}
return nodeIdFactory(node.nodeData.nonGroupingAncestor);
}
function createHierarchyLevelOptions(model, nodeId) {
if (nodeId === undefined) {
return { instanceFilter: model.rootNode.instanceFilter, hierarchyLevelSizeLimit: model.rootNode.hierarchyLimit };
}
const modelNode = model.idToNode.get(nodeId);
if (!modelNode || (0, TreeModel_js_1.isTreeModelInfoNode)(modelNode)) {
return { instanceFilter: undefined, hierarchyLevelSizeLimit: undefined };
}
return { instanceFilter: modelNode.instanceFilter, hierarchyLevelSizeLimit: modelNode.hierarchyLimit };
}
/* c8 ignore start */
class NoopTreeLoader {
loadNodes() {
return rxjs_1.EMPTY;
}
}
/* c8 ignore end */
class TimeTracker {
_onFinish;
_start;
_stopped = false;
constructor(_onFinish) {
this._onFinish = _onFinish;
this._start = Date.now();
}
[Symbol.dispose]() {
this._stopped = true;
}
finish() {
/* c8 ignore next 3 */
if (this._stopped) {
return;
}
this._stopped = true;
const elapsedTime = Date.now() - this._start;
this._onFinish(elapsedTime);
}
}
//# sourceMappingURL=TreeActions.js.map