UNPKG

@itwin/presentation-backend

Version:

Backend of iTwin.js Presentation library

356 lines • 17.8 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. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Core */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PresentationManager = exports.HierarchyCacheMode = void 0; const rxjs_1 = require("rxjs"); const rxjs_for_await_1 = require("rxjs-for-await"); const presentation_common_1 = require("@itwin/presentation-common"); const internal_1 = require("@itwin/presentation-common/internal"); const ElementPropertiesHelper_js_1 = require("./ElementPropertiesHelper.js"); const NativePlatform_js_1 = require("./NativePlatform.js"); const PresentationManagerDetail_js_1 = require("./PresentationManagerDetail.js"); const RulesetVariablesManager_js_1 = require("./RulesetVariablesManager.js"); const SelectionScopesHelper_js_1 = require("./SelectionScopesHelper.js"); const Utils_js_1 = require("./Utils.js"); const InternalSymbols_js_1 = require("./InternalSymbols.js"); /** * Presentation hierarchy cache mode. * @public */ var HierarchyCacheMode; (function (HierarchyCacheMode) { /** * Hierarchy cache is created in memory. */ HierarchyCacheMode["Memory"] = "memory"; /** * Hierarchy cache is created on disk. In this mode hierarchy cache is persisted between iModel * openings. */ HierarchyCacheMode["Disk"] = "disk"; /** * Hierarchy cache is created on disk. In this mode everything is cached in memory while creating hierarchy level * and persisted in disk cache when whole hierarchy level is created. * * **Note:** This mode is still experimental. */ HierarchyCacheMode["Hybrid"] = "hybrid"; })(HierarchyCacheMode || (exports.HierarchyCacheMode = HierarchyCacheMode = {})); /** * Backend Presentation manager which pulls the presentation data from * an iModel using native platform. * * @public */ class PresentationManager { _props; _detail; _localizationHelper; /** * Creates an instance of PresentationManager. * @param props Optional configuration properties. */ constructor(props) { this._props = props ?? {}; this._detail = new PresentationManagerDetail_js_1.PresentationManagerDetail(this._props); this._localizationHelper = new internal_1.LocalizationHelper({ getLocalizedString: props?.getLocalizedString ?? Utils_js_1.getLocalizedStringEN }); } /** Get / set active unit system used to format property values with units */ get activeUnitSystem() { return this._detail.activeUnitSystem; } /* c8 ignore next 3 */ set activeUnitSystem(value) { this._detail.activeUnitSystem = value; } /** Dispose the presentation manager. Must be called to clean up native resources. */ [Symbol.dispose]() { this._detail[Symbol.dispose](); } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ /* c8 ignore next 3 */ dispose() { this[Symbol.dispose](); } /** An event, that this manager raises whenever any request is made on it. */ get onUsed() { return this._detail.onUsed; } /** Properties used to initialize the manager */ get props() { return this._props; } /** Get rulesets manager */ rulesets() { return this._detail.rulesets; } /** * Get ruleset variables manager for specific ruleset * @param rulesetId Id of the ruleset to get variables manager for */ vars(rulesetId) { return new RulesetVariablesManager_js_1.RulesetVariablesManagerImpl(() => this._detail.getNativePlatform(), rulesetId); } /** @internal */ /* c8 ignore next 3 */ get [InternalSymbols_js_1._presentation_manager_detail]() { return this._detail; } getRulesetId(rulesetOrId) { return this._detail.getRulesetId(rulesetOrId); } /** * Retrieves nodes * @public */ async getNodes(requestOptions) { const serializedHierarchyLevel = await this._detail.getNodes(requestOptions); const hierarchyLevel = (0, internal_1.deepReplaceNullsToUndefined)(JSON.parse(serializedHierarchyLevel)); return this._localizationHelper.getLocalizedNodes(hierarchyLevel.nodes); } /** * Retrieves nodes count * @public */ async getNodesCount(requestOptions) { return this._detail.getNodesCount(requestOptions); } /** * Retrieves hierarchy level descriptor * @public */ async getNodesDescriptor(requestOptions) { const response = await this._detail.getNodesDescriptor(requestOptions); const descriptor = presentation_common_1.Descriptor.fromJSON(JSON.parse(response)); return descriptor ? this._localizationHelper.getLocalizedContentDescriptor(descriptor) : undefined; } /** * Retrieves paths from root nodes to children nodes according to specified instance key paths. Intersecting paths will be merged. * TODO: Return results in pages * @public */ async getNodePaths(requestOptions) { const result = await this._detail.getNodePaths(requestOptions); return result.map((npe) => this._localizationHelper.getLocalizedNodePathElement(npe)); } /** * Retrieves paths from root nodes to nodes containing filter text in their label. * TODO: Return results in pages * @public */ async getFilteredNodePaths(requestOptions) { const result = await this._detail.getFilteredNodePaths(requestOptions); return result.map((npe) => this._localizationHelper.getLocalizedNodePathElement(npe)); } /** * Get information about the sources of content when building it for specific ECClasses. Sources involve classes of the primary select instance, * its related instances for loading related and navigation properties. * @public */ async getContentSources(requestOptions) { return this._detail.getContentSources(requestOptions); } /** * Retrieves the content descriptor which can be used to get content * @public */ async getContentDescriptor(requestOptions) { const response = await this._detail.getContentDescriptor(requestOptions); const descriptor = presentation_common_1.Descriptor.fromJSON(JSON.parse(response)); return descriptor ? this._localizationHelper.getLocalizedContentDescriptor(descriptor) : undefined; } /** * Retrieves the content set size based on the supplied content descriptor override * @public */ async getContentSetSize(requestOptions) { return this._detail.getContentSetSize(requestOptions); } /** * Retrieves the content set based on the supplied content descriptor. * @public */ async getContentSet(requestOptions) { let items = await this._detail.getContentSet({ ...requestOptions, ...(!requestOptions.omitFormattedValues && this.props.schemaContextProvider !== undefined ? { omitFormattedValues: true } : undefined), }); if (!requestOptions.omitFormattedValues && this.props.schemaContextProvider !== undefined) { const koqPropertyFormatter = new presentation_common_1.KoqPropertyValueFormatter(this.props.schemaContextProvider(requestOptions.imodel), this.props.defaultFormats); const formatter = new internal_1.ContentFormatter(new internal_1.ContentPropertyValueFormatter(koqPropertyFormatter), requestOptions.unitSystem ?? this.props.defaultUnitSystem); items = await formatter.formatContentItems(items, requestOptions.descriptor); } return this._localizationHelper.getLocalizedContentItems(items); } /** * Retrieves the content based on the supplied content descriptor override. * @public */ async getContent(requestOptions) { const content = await this._detail.getContent({ ...requestOptions, ...(!requestOptions.omitFormattedValues && this.props.schemaContextProvider !== undefined ? { omitFormattedValues: true } : undefined), }); if (!content) { return undefined; } if (!requestOptions.omitFormattedValues && this.props.schemaContextProvider !== undefined) { const koqPropertyFormatter = new presentation_common_1.KoqPropertyValueFormatter(this.props.schemaContextProvider(requestOptions.imodel), this.props.defaultFormats); const formatter = new internal_1.ContentFormatter(new internal_1.ContentPropertyValueFormatter(koqPropertyFormatter), requestOptions.unitSystem ?? this.props.defaultUnitSystem); await formatter.formatContent(content); } return this._localizationHelper.getLocalizedContent(content); } /** * Retrieves distinct values of specific field from the content based on the supplied content descriptor override. * @param requestOptions Options for the request * @return A promise object that returns either distinct values on success or an error string on error. * @public */ async getPagedDistinctValues(requestOptions) { const result = await this._detail.getPagedDistinctValues(requestOptions); return { ...result, items: result.items.map((g) => this._localizationHelper.getLocalizedDisplayValueGroup(g)), }; } async getElementProperties(requestOptions) { if ((0, internal_1.isSingleElementPropertiesRequestOptions)(requestOptions)) { return this.getSingleElementProperties(requestOptions); } return this.getMultipleElementProperties(requestOptions); } async getSingleElementProperties(requestOptions) { const { elementId, contentParser, ...optionsNoElementId } = requestOptions; const parser = contentParser ?? internal_1.buildElementProperties; const content = await this.getContent({ ...optionsNoElementId, descriptor: { displayType: presentation_common_1.DefaultContentDisplayTypes.PropertyPane, contentFlags: presentation_common_1.ContentFlags.ShowLabels, }, rulesetOrId: "ElementProperties", keys: new presentation_common_1.KeySet([{ className: "BisCore:Element", id: elementId }]), }); if (!content || content.contentSet.length === 0) { return undefined; } return parser(content.descriptor, content.contentSet[0]); } async getMultipleElementProperties(requestOptions) { const { contentParser, batchSize: batchSizeOption, ...contentOptions } = requestOptions; const parser = contentParser ?? internal_1.buildElementProperties; const workerThreadsCount = this._props.workerThreadsCount ?? 2; // We don't want to request content for all classes at once - each class results in a huge content descriptor object that's cached in memory // and can be shared across all batch requests for that class. Handling multiple classes at the same time not only increases memory footprint, // but also may push descriptors out of cache, requiring us to recreate them, thus making performance worse. For those reasons we handle at // most `workerThreadsCount / 2` classes in parallel. /* c8 ignore next */ const classParallelism = workerThreadsCount > 1 ? Math.ceil(workerThreadsCount / 2) : 1; // We want all worker threads to be constantly busy. However, there's some fairly expensive work being done after the worker thread is done, // but before we receive the response. That means the worker thread would be starving if we sent only `workerThreadsCount` requests in parallel. // To avoid that, we keep twice as much requests active. /* c8 ignore next */ const batchesParallelism = workerThreadsCount > 0 ? workerThreadsCount : 1; /* c8 ignore next */ const batchSize = batchSizeOption ?? 100; const elementsIdentifier = (() => { if ("elementIds" in contentOptions && contentOptions.elementIds !== undefined) { const elementIds = contentOptions.elementIds; delete contentOptions.elementIds; return { elementIds }; } if ("elementClasses" in contentOptions && contentOptions.elementClasses !== undefined) { const elementClasses = contentOptions.elementClasses; delete contentOptions.elementClasses; return { elementClasses }; } /* c8 ignore next */ return { elementClasses: ["BisCore:Element"] }; })(); const descriptorGetter = async (partialProps) => this.getContentDescriptor({ ...contentOptions, displayType: presentation_common_1.DefaultContentDisplayTypes.Grid, contentFlags: presentation_common_1.ContentFlags.ShowLabels, ...partialProps }); const contentSetGetter = async (partialProps) => this.getContentSet({ ...contentOptions, ...partialProps }); const { itemBatches, count } = "elementIds" in elementsIdentifier ? (0, ElementPropertiesHelper_js_1.getContentItemsObservableFromElementIds)(requestOptions.imodel, descriptorGetter, contentSetGetter, elementsIdentifier.elementIds, classParallelism, batchesParallelism, batchSize) : (0, ElementPropertiesHelper_js_1.getContentItemsObservableFromClassNames)(requestOptions.imodel, descriptorGetter, contentSetGetter, elementsIdentifier.elementClasses, classParallelism, batchesParallelism, batchSize); return { total: await (0, rxjs_1.firstValueFrom)(count), async *iterator() { for await (const itemsBatch of (0, rxjs_for_await_1.eachValueFrom)(itemBatches)) { const { descriptor, items } = itemsBatch; yield items.map((item) => parser(descriptor, item)); } }, }; } /** * Retrieves display label definition of specific item * @public */ async getDisplayLabelDefinition(requestOptions) { const labelDefinition = await this._detail.getDisplayLabelDefinition(requestOptions); return this._localizationHelper.getLocalizedLabelDefinition(labelDefinition); } /** * Retrieves display label definitions of specific items * @public */ async getDisplayLabelDefinitions(requestOptions) { const labelDefinitions = await this._detail.getDisplayLabelDefinitions(requestOptions); return this._localizationHelper.getLocalizedLabelDefinitions(labelDefinitions); } /** * Retrieves available selection scopes. * @public * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `computeSelection` from [@itwin/unified-selection](https://github.com/iTwin/presentation/blob/master/packages/unified-selection/README.md#selection-scopes) package instead. */ // eslint-disable-next-line @typescript-eslint/no-deprecated async getSelectionScopes(_requestOptions) { return SelectionScopesHelper_js_1.SelectionScopesHelper.getSelectionScopes(); } /** * Computes selection based on provided element IDs and selection scope. * @public * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `computeSelection` from [@itwin/unified-selection](https://github.com/iTwin/presentation/blob/master/packages/unified-selection/README.md#selection-scopes) package instead. */ // eslint-disable-next-line @typescript-eslint/no-deprecated async computeSelection(requestOptions) { return SelectionScopesHelper_js_1.SelectionScopesHelper.computeSelection(requestOptions); } /** * Compares two hierarchies specified in the request options * @public */ async compareHierarchies(requestOptions) { if (!requestOptions.prev.rulesetOrId && !requestOptions.prev.rulesetVariables) { return { changes: [] }; } const { rulesetOrId, prev, rulesetVariables, ...options } = requestOptions; this._detail.registerRuleset(rulesetOrId); const currRulesetId = (0, PresentationManagerDetail_js_1.getRulesetIdObject)(requestOptions.rulesetOrId); const prevRulesetId = prev.rulesetOrId ? (0, PresentationManagerDetail_js_1.getRulesetIdObject)(prev.rulesetOrId) : currRulesetId; if (prevRulesetId.parts.id !== currRulesetId.parts.id) { throw new presentation_common_1.PresentationError(presentation_common_1.PresentationStatus.InvalidArgument, "Can't compare rulesets with different IDs"); } const currRulesetVariables = rulesetVariables ?? []; const prevRulesetVariables = prev.rulesetVariables ?? currRulesetVariables; const params = { requestId: NativePlatform_js_1.NativePlatformRequestTypes.CompareHierarchies, ...options, prevRulesetId: prevRulesetId.uniqueId, currRulesetId: currRulesetId.uniqueId, prevRulesetVariables: JSON.stringify(prevRulesetVariables), currRulesetVariables: JSON.stringify(currRulesetVariables), expandedNodeKeys: JSON.stringify(options.expandedNodeKeys ?? []), }; return JSON.parse(await this._detail.request(params)); } } exports.PresentationManager = PresentationManager; //# sourceMappingURL=PresentationManager.js.map