UNPKG

@itwin/presentation-hierarchies

Version:

A package for creating hierarchies based on data in iTwin.js iModels.

215 lines 10.1 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. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.HierarchyFilteringPath = void 0; exports.extractFilteringProps = extractFilteringProps; exports.createHierarchyFilteringHelper = createHierarchyFilteringHelper; const HierarchyNodeIdentifier_js_1 = require("./HierarchyNodeIdentifier.js"); const HierarchyNodeKey_js_1 = require("./HierarchyNodeKey.js"); /** @public */ // eslint-disable-next-line @typescript-eslint/no-redeclare var HierarchyFilteringPath; (function (HierarchyFilteringPath) { /** * Normalizes the hierarchy filtering path to the object form. * @public */ function normalize(source) { if (Array.isArray(source)) { return { path: source }; } return source; } HierarchyFilteringPath.normalize = normalize; /** * Merges two given `HierarchyFilteringPathOptions` objects. * - if both inputs are `undefined`, `undefined` is returned, * - else if one of the inputs is `undefined`, the other one is returned. * - else, merge each option individually. * * For the `autoExpand` attribute, the merge chooses to auto-expand as deep as the deepest input: * - if any one of the inputs is `true`, return `true`, * - else if both inputs are objects, return the one with the greater `depth` attribute. * - else if one input is an object, return it. * - else, return `false` or `undefined`. * * @public */ function mergeOptions(lhs, rhs) { if (!lhs || !rhs) { return lhs ?? rhs; } return { autoExpand: (() => { if (rhs.autoExpand === true || lhs.autoExpand === true) { return true; } if (typeof lhs.autoExpand === "object") { if (typeof rhs.autoExpand === "object" && rhs.autoExpand.depth > lhs.autoExpand.depth) { return rhs.autoExpand; } return lhs.autoExpand; } return rhs.autoExpand; })(), }; } HierarchyFilteringPath.mergeOptions = mergeOptions; })(HierarchyFilteringPath || (exports.HierarchyFilteringPath = HierarchyFilteringPath = {})); /** * An utility that extracts filtering properties from given root level filtering props or * the parent node. Returns `undefined` if filtering props are not present. * @public * @deprecated in 1.3. Use `createHierarchyFilteringHelper` instead. */ /* c8 ignore start */ function extractFilteringProps(rootLevelFilteringProps, parentNode) { return extractFilteringPropsInternal(rootLevelFilteringProps, parentNode); } /* c8 ignore end */ function extractFilteringPropsInternal(rootLevelFilteringProps, parentNode) { if (!parentNode) { return rootLevelFilteringProps ? { filteredNodePaths: rootLevelFilteringProps, hasFilterTargetAncestor: false } : undefined; } return parentNode.filtering?.filteredChildrenIdentifierPaths ? { filteredNodePaths: parentNode.filtering.filteredChildrenIdentifierPaths, hasFilterTargetAncestor: !!parentNode.filtering.hasFilterTargetAncestor || !!parentNode.filtering.isFilterTarget, } : undefined; } /** * Creates a set of utilities for making it easier to filter the given hierarchy * level. * * @public */ function createHierarchyFilteringHelper(rootLevelFilteringProps, parentNode) { const filteringProps = extractFilteringPropsInternal(rootLevelFilteringProps, parentNode); const hasFilter = !!filteringProps; return { /** * Returns a flag indicating if the hierarchy level is filtered. */ hasFilter, /** * Returns a flag indicating whether this hierarchy level has an ancestor node * that is a filter target. That generally means that this and all downstream hierarchy * levels should be displayed without filter being applied to them, even if filter paths * say otherwise. */ hasFilterTargetAncestor: filteringProps?.hasFilterTargetAncestor ?? false, /** * Returns a list of hierarchy node identifiers that apply specifically for this * hierarchy level. Returns `undefined` if filtering is not applied to this level. */ getChildNodeFilteringIdentifiers: () => { if (!hasFilter) { return undefined; } return filteringProps.filteredNodePaths .map(HierarchyFilteringPath.normalize) .filter(({ path }) => path.length > 0) .map(({ path }) => path[0]); }, /** * When a hierarchy node is created for a filtered hierarchy level, it needs some attributes (e.g. `filtering` * and `autoExpand`) to be set based on the filter paths and filtering options. This function calculates * these props for a child node based on its key or path matcher. * * When using `pathMatcher` prop, callers have more flexibility to decide whether the given `HierarchyNodeIdentifier` applies * to their node. For example, only some parts of the identifier can be checked for improved performance. Otherwise, the * `nodeKey` prop can be used to check the whole identifier. */ createChildNodeProps: (props) => { if (!hasFilter) { return undefined; } const reducer = new MatchingFilteringPathsReducer(filteringProps?.hasFilterTargetAncestor); filteringProps.filteredNodePaths.forEach((filteredPath) => { const normalizedPath = HierarchyFilteringPath.normalize(filteredPath); if ("nodeKey" in props && ((HierarchyNodeKey_js_1.HierarchyNodeKey.isGeneric(props.nodeKey) && HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.equal(normalizedPath.path[0], props.nodeKey)) || (HierarchyNodeKey_js_1.HierarchyNodeKey.isInstances(props.nodeKey) && props.nodeKey.instanceKeys.some((ik) => HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.equal(normalizedPath.path[0], ik))))) { reducer.accept(normalizedPath); } else if ("pathMatcher" in props && props.pathMatcher(normalizedPath.path[0])) { reducer.accept(normalizedPath); } }); return reducer.getNodeProps(); }, /** * Similar to `createChildNodeProps`, but takes an async `pathMatcher` prop. */ createChildNodePropsAsync: (props) => { if (!hasFilter) { return undefined; } const reducer = new MatchingFilteringPathsReducer(filteringProps?.hasFilterTargetAncestor); const matchedPathPromises = new Array(); for (const filteredChildrenNodeIdentifierPath of filteringProps.filteredNodePaths) { const normalizedPath = HierarchyFilteringPath.normalize(filteredChildrenNodeIdentifierPath); /* c8 ignore next 3 */ if (normalizedPath.path.length === 0) { continue; } const matchesPossiblyPromise = props.pathMatcher(normalizedPath.path[0]); if (matchesPossiblyPromise instanceof Promise) { matchedPathPromises.push(matchesPossiblyPromise.then((matches) => (matches ? normalizedPath : undefined))); continue; } if (!matchesPossiblyPromise) { continue; } reducer.accept(normalizedPath); } if (matchedPathPromises.length === 0) { return reducer.getNodeProps(); } return Promise.all(matchedPathPromises) .then((matchedPath) => matchedPath.forEach((normalizedPath) => normalizedPath && reducer.accept(normalizedPath))) .then(() => reducer.getNodeProps()); }, }; } class MatchingFilteringPathsReducer { _hasFilterTargetAncestor; _filteredChildrenIdentifierPaths = new Array(); _isFilterTarget = false; _filterTargetOptions = undefined; _needsAutoExpand = false; constructor(_hasFilterTargetAncestor) { this._hasFilterTargetAncestor = _hasFilterTargetAncestor; } accept({ path, options }) { if (path.length === 1) { this._isFilterTarget = true; this._filterTargetOptions = HierarchyFilteringPath.mergeOptions(this._filterTargetOptions, options); } else if (path.length > 1) { this._filteredChildrenIdentifierPaths.push({ path: path.slice(1), options }); if (options?.autoExpand) { this._needsAutoExpand = true; } } } getNodeProps() { return { ...(this._hasFilterTargetAncestor || this._isFilterTarget || this._filteredChildrenIdentifierPaths.length > 0 ? { filtering: { ...(this._hasFilterTargetAncestor ? { hasFilterTargetAncestor: true } : undefined), ...(this._isFilterTarget ? { isFilterTarget: true, filterTargetOptions: this._filterTargetOptions } : undefined), ...(this._filteredChildrenIdentifierPaths.length > 0 ? { filteredChildrenIdentifierPaths: this._filteredChildrenIdentifierPaths } : undefined), }, } : undefined), ...(this._needsAutoExpand ? { autoExpand: true } : undefined), }; } } //# sourceMappingURL=HierarchyFiltering.js.map