@itwin/presentation-components
Version:
React components based on iTwin.js Presentation library
309 lines • 15.4 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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 */
/** @packageDocumentation
* @module Tree
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PresentationTreeDataProvider = void 0;
require("../common/DisposePolyfill.js");
const components_react_1 = require("@itwin/components-react");
const core_bentley_1 = require("@itwin/core-bentley");
const presentation_common_1 = require("@itwin/presentation-common");
const presentation_frontend_1 = require("@itwin/presentation-frontend");
const Diagnostics_js_1 = require("../common/Diagnostics.js");
const Utils_js_1 = require("../common/Utils.js");
const ComponentsLoggerCategory_js_1 = require("../ComponentsLoggerCategory.js");
const PresentationFilterBuilder_js_1 = require("../instance-filter-builder/PresentationFilterBuilder.js");
const PresentationTreeNodeItem_js_1 = require("./PresentationTreeNodeItem.js");
const Utils_js_2 = require("./Utils.js");
/**
* Presentation Rules-driven tree data provider.
* @public
* @deprecated in 5.7. All tree-related APIs have been deprecated in favor of the new generation hierarchy
* building APIs (see https://github.com/iTwin/presentation/blob/33e79ee8d77f30580a9bab81a72884bda008db25/README.md#the-packages).
*/
class PresentationTreeDataProvider {
_unregisterVariablesChangeListener;
_dataSource;
_diagnosticsOptions;
_onHierarchyLimitExceeded;
_props;
hierarchyLevelSizeLimit;
/** Constructor. */
constructor(props) {
this._props = { ...props };
this._dataSource = {
getNodesIterator: async (requestOptions) => {
// we can't just drop support for the `getNodesAndCount` override, so if it's set - need to take data from it
if (props.dataSourceOverrides?.getNodesAndCount) {
return createNodesIteratorFromDeprecatedResponse(await props.dataSourceOverrides.getNodesAndCount(requestOptions));
}
// the `PresentationManager.getNodesIterator` has only been added to @itwin/presentation-frontend in 4.5.1, and our peerDependency is
// set to 4.0.0, so we need to check if the method is really there
if (presentation_frontend_1.Presentation.presentation.getNodesIterator) {
return presentation_frontend_1.Presentation.presentation.getNodesIterator(requestOptions);
}
return createNodesIteratorFromDeprecatedResponse(await presentation_frontend_1.Presentation.presentation.getNodesAndCount(requestOptions));
},
getFilteredNodePaths: async (requestOptions) => presentation_frontend_1.Presentation.presentation.getFilteredNodePaths(requestOptions),
...props.dataSourceOverrides,
};
this._diagnosticsOptions = (0, Diagnostics_js_1.createDiagnosticsOptions)(props);
this.hierarchyLevelSizeLimit = props.hierarchyLevelSizeLimit;
this._onHierarchyLimitExceeded = props.onHierarchyLimitExceeded;
}
#dispose() {
this._unregisterVariablesChangeListener?.();
this._unregisterVariablesChangeListener = undefined;
}
/** Destructor. Must be called to clean up. */
[Symbol.dispose]() {
this.#dispose();
}
/** @deprecated in 5.7. Use `[Symbol.dispose]` instead. */
/* c8 ignore next 3 */
dispose() {
this.#dispose();
}
get props() {
return this._props;
}
/** Id of the ruleset used by this data provider */
get rulesetId() {
return (0, Utils_js_1.getRulesetId)(this.props.ruleset);
}
/** [IModelConnection]($core-frontend) used by this data provider */
get imodel() {
return this.props.imodel;
}
/**
* Paging options for obtaining nodes.
* @see `PresentationTreeDataProviderProps.pagingSize`
*/
get pagingSize() {
return this.props.pagingSize;
}
set pagingSize(value) {
this._props.pagingSize = value;
}
/** Called to get base options for requests */
createBaseRequestOptions() {
return {
imodel: this.props.imodel,
rulesetOrId: this.props.ruleset,
...(this._diagnosticsOptions ? { diagnostics: this._diagnosticsOptions } : undefined),
};
}
/** Called to get options for node requests */
createPagedRequestOptions(parentKey, pageOptions, instanceFilter) {
const isPaging = pageOptions && (pageOptions.start || pageOptions.size !== undefined);
return {
...this.createRequestOptions(parentKey, instanceFilter),
...(isPaging ? { paging: (0, Utils_js_2.pageOptionsUiToPresentation)(pageOptions) } : undefined),
};
}
/** Creates options for nodes requests. */
createRequestOptions(parentKey, instanceFilter) {
const isHierarchyLevelLimitingSupported = !!this.hierarchyLevelSizeLimit && parentKey;
return {
...this.createBaseRequestOptions(),
...(parentKey ? { parentKey } : undefined),
...(isHierarchyLevelLimitingSupported ? { sizeLimit: this.hierarchyLevelSizeLimit } : undefined),
...(instanceFilter ? { instanceFilter } : undefined),
};
}
/**
* Returns a [NodeKey]($presentation-common) from given [TreeNodeItem]($components-react).
*
* **Warning**: Returns invalid [NodeKey]($presentation-common) if `node` is not a [[PresentationTreeNodeItem]].
*
* @deprecated in 4.0. Use [[isPresentationTreeNodeItem]] and [[PresentationTreeNodeItem.key]] to get [NodeKey]($presentation-common).
*/
getNodeKey(node) {
const invalidKey = { type: "", pathFromRoot: [], version: 0 };
return (0, PresentationTreeNodeItem_js_1.isPresentationTreeNodeItem)(node) ? node.key : invalidKey;
}
/**
* Returns nodes
* @param parentNode The parent node to return children for.
* @param pageOptions Information about the requested page of data.
*/
async getNodes(parentNode, pageOptions) {
if (undefined !== pageOptions && pageOptions.size !== this.pagingSize) {
const msg = `PresentationTreeDataProvider.pagingSize doesn't match pageOptions in PresentationTreeDataProvider.getNodes call.
Make sure you set PresentationTreeDataProvider.pagingSize to avoid excessive backend requests.`;
core_bentley_1.Logger.logWarning(ComponentsLoggerCategory_js_1.PresentationComponentsLoggerCategory.Hierarchy, msg);
}
const instanceFilter = await getFilterDefinition(this.imodel, parentNode);
return (await this._getNodesAndCount(parentNode, pageOptions, instanceFilter)).nodes;
}
/**
* Returns the total number of nodes
* @param parentNode The parent node to return children count for.
*/
async getNodesCount(parentNode) {
const instanceFilter = await getFilterDefinition(this.imodel, parentNode);
return (await this._getNodesAndCount(parentNode, { start: 0, size: this.pagingSize }, instanceFilter)).count;
}
_getNodesAndCount = (0, Utils_js_1.memoize)(async (parentNode, pageOptions, instanceFilter) => {
this.setupRulesetVariablesListener();
const parentKey = parentNode && (0, PresentationTreeNodeItem_js_1.isPresentationTreeNodeItem)(parentNode) ? parentNode.key : undefined;
const requestOptions = this.createPagedRequestOptions(parentKey, pageOptions, instanceFilter);
return createNodesAndCountResult(async () => this._dataSource.getNodesIterator(requestOptions), this.createBaseRequestOptions(), (node, parentId) => (0, Utils_js_2.createTreeNodeItem)(node, parentId, this.props), parentNode, this.hierarchyLevelSizeLimit, this._onHierarchyLimitExceeded);
},
// eslint-disable-next-line @typescript-eslint/unbound-method
{ isMatchingKey: MemoizationHelpers.areNodesRequestsEqual });
/**
* Returns filtered node paths.
* @param filter Filter.
*/
async getFilteredNodePaths(filter) {
return this._dataSource.getFilteredNodePaths({
...this.createBaseRequestOptions(),
filterText: filter,
});
}
setupRulesetVariablesListener() {
if (this._unregisterVariablesChangeListener) {
return;
}
this._unregisterVariablesChangeListener = presentation_frontend_1.Presentation.presentation.vars((0, Utils_js_1.getRulesetId)(this.props.ruleset)).onVariableChanged.addListener(() => {
this._getNodesAndCount.cache.values.length = 0;
this._getNodesAndCount.cache.keys.length = 0;
});
}
}
exports.PresentationTreeDataProvider = PresentationTreeDataProvider;
async function getFilterDefinition(imodel, node) {
if (!node || !(0, PresentationTreeNodeItem_js_1.isPresentationTreeNodeItem)(node) || !node.filtering) {
return undefined;
}
// combine ancestors and current filters
const allFilters = [...node.filtering.ancestorFilters, ...(node.filtering.active ? [node.filtering.active] : [])];
if (allFilters.length === 0) {
return undefined;
}
if (allFilters.length === 1) {
return (0, PresentationFilterBuilder_js_1.createInstanceFilterDefinition)(allFilters[0], imodel);
}
const appliedFilters = allFilters.map((filterInfo) => filterInfo.filter).filter((filter) => filter !== undefined);
const usedClasses = getConcatenatedDistinctClassInfos(allFilters);
// if there are more than one filter applied, combine them using `AND` operator
// otherwise apply filter directly
const info = {
filter: appliedFilters.length > 0
? {
operator: components_react_1.PropertyFilterRuleGroupOperator.And,
conditions: appliedFilters,
}
: undefined,
usedClasses,
};
return (0, PresentationFilterBuilder_js_1.createInstanceFilterDefinition)(info, imodel);
}
function getConcatenatedDistinctClassInfos(appliedFilters) {
const concatenatedClassInfos = appliedFilters.reduce((accumulator, value) => [...accumulator, ...value.usedClasses], []);
return [...new Map(concatenatedClassInfos.map((item) => [item.id, item])).values()];
}
async function createNodesAndCountResult(resultFactory, baseOptions, treeItemFactory, parentNode, hierarchyLevelSizeLimit, onHierarchyLimitExceeded) {
try {
const result = await resultFactory();
const { items, total: count } = result;
const isParentFiltered = parentNode && (0, PresentationTreeNodeItem_js_1.isPresentationTreeNodeItem)(parentNode) && parentNode.filtering?.active;
if (count === 0 && isParentFiltered) {
return createStatusNodeResult(parentNode, "tree.no-filtered-children", PresentationTreeNodeItem_js_1.InfoTreeNodeItemType.NoChildren);
}
return { nodes: await createTreeItems(items, baseOptions, treeItemFactory, parentNode), count };
}
catch (e) {
if (e instanceof Error) {
if (hasErrorNumber(e)) {
switch (e.errorNumber) {
case presentation_common_1.PresentationStatus.Canceled:
return { nodes: [], count: 0 };
case presentation_common_1.PresentationStatus.BackendTimeout:
return createStatusNodeResult(parentNode, "tree.timeout", PresentationTreeNodeItem_js_1.InfoTreeNodeItemType.BackendTimeout);
case presentation_common_1.PresentationStatus.ResultSetTooLarge:
// ResultSetTooLarge error can't occur if hierarchyLevelSizeLimit is undefined.
onHierarchyLimitExceeded?.();
return {
nodes: [
(0, Utils_js_2.createInfoNode)(parentNode, `${(0, Utils_js_1.translate)("tree.result-limit-exceeded")} ${hierarchyLevelSizeLimit}.`, PresentationTreeNodeItem_js_1.InfoTreeNodeItemType.ResultSetTooLarge),
],
count: 1,
};
}
}
// eslint-disable-next-line no-console
console.error(`Error creating nodes: ${e.toString()}`);
}
return createStatusNodeResult(parentNode, "tree.unknown-error");
}
}
function createStatusNodeResult(parentNode, labelKey, type) {
return {
nodes: [(0, Utils_js_2.createInfoNode)(parentNode, (0, Utils_js_1.translate)(labelKey), type)],
count: 1,
};
}
async function createTreeItems(nodes, baseOptions, treeItemFactory, parentNode) {
const items = [];
// collect filters for child elements. These filter will be applied for grouping nodes
// if current node has `ancestorFilters` it means it is grouping node and those filter should be forwarded to child grouping nodes alongside current node filter.
// if current node does not have `ancestorFilters` it means it is an instance node and only it's filter should be applied to child grouping nodes.
const ancestorFilters = parentNode && (0, PresentationTreeNodeItem_js_1.isPresentationTreeNodeItem)(parentNode) && parentNode.filtering
? [...parentNode.filtering.ancestorFilters, ...(parentNode.filtering.active ? [parentNode.filtering.active] : [])]
: [];
for await (const node of nodes) {
const item = treeItemFactory(node, parentNode?.id);
if (node.supportsFiltering) {
item.filtering = {
descriptor: async () => {
const descriptor = await presentation_frontend_1.Presentation.presentation.getNodesDescriptor({ ...baseOptions, parentKey: node.key });
if (!descriptor) {
throw new presentation_common_1.PresentationError(presentation_common_1.PresentationStatus.Error, `Failed to get descriptor for node - ${node.label.displayValue}`);
}
return descriptor;
},
ancestorFilters: presentation_common_1.NodeKey.isGroupingNodeKey(item.key) ? ancestorFilters : [],
};
}
items.push(item);
}
return items;
}
class MemoizationHelpers {
static areNodesRequestsEqual(lhsArgs, rhsArgs) {
if (lhsArgs[0]?.id !== rhsArgs[0]?.id) {
return false;
}
if ((lhsArgs[1]?.start ?? 0) !== (rhsArgs[1]?.start ?? 0)) {
return false;
}
if ((lhsArgs[1]?.size ?? 0) !== (rhsArgs[1]?.size ?? 0)) {
return false;
}
if (lhsArgs[2]?.expression !== rhsArgs[2]?.expression) {
return false;
}
return true;
}
}
function createNodesIteratorFromDeprecatedResponse({ count, nodes }) {
return {
total: count,
items: (async function* () {
for (const node of nodes) {
yield node;
}
})(),
};
}
function hasErrorNumber(e) {
return "errorNumber" in e && e.errorNumber !== undefined;
}
//# sourceMappingURL=DataProvider.js.map