UNPKG

@itwin/presentation-frontend

Version:

Frontend of iModel.js Presentation library

583 lines • 29.2 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 = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_frontend_1 = require("@itwin/core-frontend"); const presentation_common_1 = require("@itwin/presentation-common"); const internal_1 = require("@itwin/presentation-common/internal"); const unified_selection_1 = require("@itwin/unified-selection"); const IModelConnectionInitialization_js_1 = require("./IModelConnectionInitialization.js"); const InternalSymbols_js_1 = require("./InternalSymbols.js"); const IpcRequestsHandler_js_1 = require("./IpcRequestsHandler.js"); const LocalizationHelper_js_1 = require("./LocalizationHelper.js"); const RulesetManager_js_1 = require("./RulesetManager.js"); const RulesetVariablesManager_js_1 = require("./RulesetVariablesManager.js"); const StreamedResponseGenerator_js_1 = require("./StreamedResponseGenerator.js"); /** * Frontend Presentation manager which basically just forwards all calls to * the backend implementation. * * @public */ class PresentationManager { _requestsHandler; _rulesets; _localizationHelper; _explicitActiveUnitSystem; _rulesetVars; _clearEventListener; _schemaContextProvider; // eslint-disable-next-line @typescript-eslint/no-deprecated _defaultFormats; _ipcRequestsHandler; /** * An event raised when hierarchies created using specific ruleset change. * * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ // eslint-disable-next-line @typescript-eslint/no-deprecated onIModelHierarchyChanged = new core_bentley_1.BeEvent(); /** * An event raised when content created using specific ruleset changes */ onIModelContentChanged = new core_bentley_1.BeEvent(); /** * Get / set active unit system used to format property values with units. * * @deprecated in 4.0 - will not be removed until after 2026-06-13. `IModelApp.quantityFormatter` should be used to get/set the active unit system. At the moment * [[PresentationManager]] allows overriding it, but returns `IModelApp.quantityFormatter.activeUnitSystem` if override * is not set. */ get activeUnitSystem() { return this._explicitActiveUnitSystem ?? core_frontend_1.IModelApp.quantityFormatter.activeUnitSystem; } set activeUnitSystem(value) { this._explicitActiveUnitSystem = value; } constructor(props) { if (props) { // eslint-disable-next-line @typescript-eslint/no-deprecated this._explicitActiveUnitSystem = props.activeUnitSystem; } this._requestsHandler = props?.rpcRequestsHandler ?? new internal_1.RpcRequestsHandler(props ? { clientId: props.clientId, timeout: props.requestTimeout } : undefined); this._rulesetVars = new Map(); this._rulesets = RulesetManager_js_1.RulesetManagerImpl.create(); this._localizationHelper = new LocalizationHelper_js_1.FrontendLocalizationHelper(props?.activeLocale); // eslint-disable-next-line @typescript-eslint/no-deprecated this._schemaContextProvider = props?.schemaContextProvider ?? ((imodel) => imodel.schemaContext); // eslint-disable-next-line @typescript-eslint/no-deprecated this._defaultFormats = props?.defaultFormats; if (core_frontend_1.IpcApp.isValid) { // Ipc only works in ipc apps, so the `onUpdate` callback will only be called there. this._clearEventListener = core_frontend_1.IpcApp.addListener(internal_1.PresentationIpcEvents.Update, this.onUpdate); this._ipcRequestsHandler = props?.ipcRequestsHandler ?? new IpcRequestsHandler_js_1.IpcRequestsHandler(this._requestsHandler.clientId); } } /** Get / set active locale used for localizing presentation data */ get activeLocale() { return this._localizationHelper.locale; } set activeLocale(locale) { this._localizationHelper.locale = locale; } [Symbol.dispose]() { if (this._clearEventListener) { this._clearEventListener(); this._clearEventListener = undefined; } } /** @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](); } onUpdate = (_evt, report) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.handleUpdateAsync(report); }; /** @note This is only called in native apps after changes in iModels */ async handleUpdateAsync(report) { for (const imodelKey in report) { /* c8 ignore next 3 */ if (!report.hasOwnProperty(imodelKey)) { continue; } const imodelReport = report[imodelKey]; for (const rulesetId in imodelReport) { /* c8 ignore next 3 */ if (!imodelReport.hasOwnProperty(rulesetId)) { continue; } const updateInfo = imodelReport[rulesetId]; if (updateInfo.content) { this.onIModelContentChanged.raiseEvent({ rulesetId, updateInfo: updateInfo.content, imodelKey }); } // eslint-disable-next-line @typescript-eslint/no-deprecated if (updateInfo.hierarchy) { // eslint-disable-next-line @typescript-eslint/no-deprecated this.onIModelHierarchyChanged.raiseEvent({ rulesetId, updateInfo: updateInfo.hierarchy, imodelKey }); } } } } /** * Create a new PresentationManager instance * @param props Optional properties used to configure the manager */ static create(props) { return new PresentationManager(props); } /** @internal */ get [InternalSymbols_js_1._presentation_manager_rpcRequestsHandler]() { return this._requestsHandler; } /** @internal */ get [InternalSymbols_js_1._presentation_manager_ipcRequestsHandler]() { return this._ipcRequestsHandler; } /** * Get rulesets manager */ rulesets() { return this._rulesets; } /** * Get ruleset variables manager for specific ruleset * @param rulesetId Id of the ruleset to get the vars manager for */ vars(rulesetId) { if (!this._rulesetVars.has(rulesetId)) { const varsManager = new RulesetVariablesManager_js_1.RulesetVariablesManagerImpl(rulesetId, this._ipcRequestsHandler); this._rulesetVars.set(rulesetId, varsManager); } return this._rulesetVars.get(rulesetId); } toRpcTokenOptions(requestOptions) { // 1. put default `locale` and `unitSystem` // 2. put all `requestOptions` members (if `locale` or `unitSystem` are set, they'll override the defaults put at #1) // 3. put `imodel` of type `IModelRpcProps` which overwrites the `imodel` from `requestOptions` put at #2 const defaultOptions = {}; if (this.activeLocale) { defaultOptions.locale = this.activeLocale; } defaultOptions.unitSystem = this.activeUnitSystem; // eslint-disable-line @typescript-eslint/no-deprecated const { imodel, rulesetVariables, ...rpcRequestOptions } = requestOptions; return { ...defaultOptions, ...rpcRequestOptions, ...(rulesetVariables ? { rulesetVariables: rulesetVariables.map(presentation_common_1.RulesetVariable.toJSON) } : {}), imodel: imodel.getRpcProps(), }; } async addRulesetAndVariablesToOptions(options) { const { rulesetOrId, rulesetVariables } = options; let foundRulesetOrId; if (typeof rulesetOrId === "object") { foundRulesetOrId = rulesetOrId; } else { const foundRuleset = await this._rulesets.get(rulesetOrId); foundRulesetOrId = foundRuleset ? foundRuleset.toJSON() : rulesetOrId; } const rulesetId = typeof foundRulesetOrId === "object" ? foundRulesetOrId.id : foundRulesetOrId; // All Id64Array variable values must be sorted for serialization to JSON to work. RulesetVariablesManager // sorts them before storing, so that part is taken care of, but we need to ensure that variables coming from // request options are also sorted. const variables = (rulesetVariables ?? []).map((variable) => { if (variable.type === presentation_common_1.VariableValueTypes.Id64Array) { return { ...variable, value: core_bentley_1.OrderedId64Iterable.sortArray(variable.value) }; } return variable; }); if (!this._ipcRequestsHandler) { // only need to add variables from variables manager if there's no IPC // handler - if there is one, the variables are already known by the backend variables.push(...this.vars(rulesetId).getAllVariables()); } return { ...options, rulesetOrId: foundRulesetOrId, rulesetVariables: variables }; } /* eslint-disable @typescript-eslint/no-deprecated */ /** * Returns an iterator that polls nodes asynchronously. * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ async getNodesIterator(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options }); const generator = new StreamedResponseGenerator_js_1.StreamedResponseGenerator({ ...requestOptions, getBatch: async (paging) => { const result = await this._requestsHandler.getPagedNodes({ ...rpcOptions, paging }); return { total: result.total, items: this._localizationHelper.getLocalizedNodes(result.items), }; }, }); return generator.createAsyncIteratorResponse(); } /** * Retrieves nodes. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getNodesIterator]] instead. */ async getNodes(requestOptions) { const result = await this.getNodesIterator(requestOptions); return collect(result.items); } /** * Retrieves nodes count. * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ async getNodesCount(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options }); return this._requestsHandler.getNodesCount(rpcOptions); } /** * Retrieves total nodes count and a single page of nodes. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getNodesIterator]] instead. */ async getNodesAndCount(requestOptions) { const result = await this.getNodesIterator(requestOptions); return { count: result.total, nodes: await collect(result.items), }; } /** * Retrieves hierarchy level descriptor. * @public * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ async getNodesDescriptor(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); try { const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options }); const result = await this._requestsHandler.getNodesDescriptor(rpcOptions); const descriptor = presentation_common_1.Descriptor.fromJSON(result); return descriptor ? this._localizationHelper.getLocalizedContentDescriptor(descriptor) : undefined; } finally { await (0, IModelConnectionInitialization_js_1.ensureIModelInitialized)(requestOptions.imodel); } } /** * Retrieves paths from root nodes to children nodes according to specified keys. Intersecting paths will be merged. * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ async getNodePaths(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options }); const result = await this._requestsHandler.getNodePaths(rpcOptions); return result.map((npe) => this._localizationHelper.getLocalizedNodePathElement(npe)); } /** * Retrieves paths from root nodes to nodes containing filter text in their label. * @deprecated in 5.2 - will not be removed until after 2026-10-01. Use the new [@itwin/presentation-hierarchies](https://github.com/iTwin/presentation/blob/master/packages/hierarchies/README.md) * package for creating hierarchies. */ async getFilteredNodePaths(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const result = await this._requestsHandler.getFilteredNodePaths(this.toRpcTokenOptions(options)); return result.map((npe) => this._localizationHelper.getLocalizedNodePathElement(npe)); } /* eslint-enable @typescript-eslint/no-deprecated */ /** * 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) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const rpcOptions = this.toRpcTokenOptions(requestOptions); const result = await this._requestsHandler.getContentSources(rpcOptions); return result.sources.map((sourceJson) => presentation_common_1.SelectClassInfo.fromCompressedJSON(sourceJson, result.classesMap)); } /** Retrieves the content descriptor which describes the content and can be used to customize it. */ async getContentDescriptor(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); try { const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options, keys: stripTransientElementKeys(options.keys).toJSON(), }); const result = await this._requestsHandler.getContentDescriptor(rpcOptions); const descriptor = presentation_common_1.Descriptor.fromJSON(result); return descriptor ? this._localizationHelper.getLocalizedContentDescriptor(descriptor) : undefined; } finally { await (0, IModelConnectionInitialization_js_1.ensureIModelInitialized)(requestOptions.imodel); } } /** Retrieves overall content set size. */ async getContentSetSize(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = this.toRpcTokenOptions({ ...options, descriptor: getDescriptorOverrides(requestOptions.descriptor), keys: stripTransientElementKeys(requestOptions.keys).toJSON(), }); return this._requestsHandler.getContentSetSize(rpcOptions); } async getContentIteratorInternal(requestOptions) { const options = await this.addRulesetAndVariablesToOptions(requestOptions); const firstPageSize = options.batchSize ?? requestOptions.paging?.size; const rpcOptions = this.toRpcTokenOptions({ ...options, descriptor: getDescriptorOverrides(requestOptions.descriptor), keys: stripTransientElementKeys(requestOptions.keys).toJSON(), omitFormattedValues: true, ...(firstPageSize ? { paging: { ...requestOptions.paging, size: firstPageSize } } : undefined), }); let contentFormatter; if (!requestOptions.omitFormattedValues) { const schemaContext = this._schemaContextProvider(requestOptions.imodel); const unitSystem = requestOptions.unitSystem ?? this._explicitActiveUnitSystem ?? core_frontend_1.IModelApp.quantityFormatter.activeUnitSystem; const koqPropertyFormatter = new presentation_common_1.KoqPropertyValueFormatter({ schemaContext, formatsProvider: core_frontend_1.IModelApp.formatsProvider, }); koqPropertyFormatter.defaultFormats = this._defaultFormats; contentFormatter = new internal_1.ContentFormatter(new internal_1.ContentPropertyValueFormatter(koqPropertyFormatter), unitSystem); } let descriptor = requestOptions.descriptor instanceof presentation_common_1.Descriptor ? requestOptions.descriptor : undefined; let firstPage; if (!descriptor) { const firstPageResponse = await this._requestsHandler.getPagedContent(rpcOptions); if (!firstPageResponse?.descriptor || !firstPageResponse.contentSet) { return undefined; } descriptor = presentation_common_1.Descriptor.fromJSON(firstPageResponse?.descriptor); firstPage = firstPageResponse?.contentSet; } /* c8 ignore next 3 */ if (!descriptor) { return undefined; } descriptor = this._localizationHelper.getLocalizedContentDescriptor(descriptor); const getPage = async (paging, requestIndex) => { let contentSet = requestIndex === 0 ? firstPage : undefined; contentSet ??= await this._requestsHandler.getPagedContentSet({ ...rpcOptions, paging }); let items = contentSet.items.map((x) => presentation_common_1.Item.fromJSON(x)).filter((x) => x !== undefined); if (contentFormatter) { items = await contentFormatter.formatContentItems(items, descriptor); } items = this._localizationHelper.getLocalizedContentItems(items); return { total: contentSet.total, items, }; }; const generator = new StreamedResponseGenerator_js_1.StreamedResponseGenerator({ ...requestOptions, getBatch: getPage, }); return { ...(await generator.createAsyncIteratorResponse()), descriptor, }; } /** Retrieves a content descriptor, item count and async generator for the items themselves. */ async getContentIterator(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const response = await this.getContentIteratorInternal(requestOptions); if (!response) { return undefined; } await (0, IModelConnectionInitialization_js_1.ensureIModelInitialized)(requestOptions.imodel); return response; } /** * Retrieves content which consists of a content descriptor and a page of records. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getContentIterator]] instead. */ async getContent(requestOptions) { // eslint-disable-next-line @typescript-eslint/no-deprecated return (await this.getContentAndSize(requestOptions))?.content; } /** * Retrieves content set size and content which consists of a content descriptor and a page of records. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getContentIterator]] instead. */ async getContentAndSize(requestOptions) { const response = await this.getContentIterator(requestOptions); if (!response) { return undefined; } const { descriptor, total } = response; const items = await collect(response.items); return { content: new presentation_common_1.Content(descriptor, items), size: total, }; } /** Returns an iterator that asynchronously polls distinct values of specific field from the content. */ async getDistinctValuesIterator(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = { ...this.toRpcTokenOptions(options), descriptor: getDescriptorOverrides(options.descriptor), keys: stripTransientElementKeys(options.keys).toJSON(), }; const generator = new StreamedResponseGenerator_js_1.StreamedResponseGenerator({ ...requestOptions, getBatch: async (paging) => { const response = await this._requestsHandler.getPagedDistinctValues({ ...rpcOptions, paging }); return { total: response.total, items: response.items.map((x) => this._localizationHelper.getLocalizedDisplayValueGroup(x)), }; }, }); return generator.createAsyncIteratorResponse(); } /** * Retrieves distinct values of specific field from the content. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getDistinctValuesIterator]] instead. */ async getPagedDistinctValues(requestOptions) { const result = await this.getDistinctValuesIterator(requestOptions); return { total: result.total, items: await collect(result.items), }; } /** * Retrieves property data in a simplified format for a single element specified by ID. * @public */ async getElementProperties(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const { elementId, contentParser, ...optionsNoElementId } = requestOptions; const parser = contentParser ?? internal_1.buildElementProperties; const iter = await this.getContentIterator({ ...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 (!iter || iter.total === 0) { return undefined; } return parser(iter.descriptor, (await iter.items.next()).value); } /** * Retrieves content item instance keys. * @public */ async getContentInstanceKeys(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const options = await this.addRulesetAndVariablesToOptions(requestOptions); const rpcOptions = { ...this.toRpcTokenOptions(options), keys: stripTransientElementKeys(options.keys).toJSON(), }; const generator = new StreamedResponseGenerator_js_1.StreamedResponseGenerator({ ...requestOptions, getBatch: async (page) => { const keys = await this._requestsHandler.getContentInstanceKeys({ ...rpcOptions, paging: page }); return { total: keys.total, items: keys.items.instanceKeys.reduce((instanceKeys, entry) => { for (const id of core_bentley_1.CompressedId64Set.iterable(entry[1])) { instanceKeys.push({ className: entry[0], id }); } return instanceKeys; }, new Array()), }; }, }); const { total, items } = await generator.createAsyncIteratorResponse(); return { total, async *items() { yield* items; }, }; } /** Retrieves display label definition of specific item. */ async getDisplayLabelDefinition(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const rpcOptions = this.toRpcTokenOptions({ ...requestOptions }); const result = await this._requestsHandler.getDisplayLabelDefinition(rpcOptions); return this._localizationHelper.getLocalizedLabelDefinition(result); } /** Retrieves display label definition of specific items. */ async getDisplayLabelDefinitionsIterator(requestOptions) { (0, IModelConnectionInitialization_js_1.startIModelInitialization)(requestOptions.imodel); const rpcOptions = this.toRpcTokenOptions({ ...requestOptions }); const generator = new StreamedResponseGenerator_js_1.StreamedResponseGenerator({ ...requestOptions, getBatch: async (page) => { const partialKeys = !page.start ? rpcOptions.keys : rpcOptions.keys.slice(page.start); const result = await this._requestsHandler.getPagedDisplayLabelDefinitions({ ...rpcOptions, keys: partialKeys }); result.items = this._localizationHelper.getLocalizedLabelDefinitions(result.items); return result; }, }); return generator.createAsyncIteratorResponse(); } /** * Retrieves display label definition of specific items. * @deprecated in 4.5 - will not be removed until after 2026-06-13. Use [[getDisplayLabelDefinitionsIterator]] instead. */ async getDisplayLabelDefinitions(requestOptions) { const { items } = await this.getDisplayLabelDefinitionsIterator(requestOptions); return collect(items); } } exports.PresentationManager = PresentationManager; const getDescriptorOverrides = (descriptorOrOverrides) => { if (descriptorOrOverrides instanceof presentation_common_1.Descriptor) { return descriptorOrOverrides.createDescriptorOverrides(); } return descriptorOrOverrides; }; const stripTransientElementKeys = (keys) => { if (!keys.some((key) => presentation_common_1.Key.isInstanceKey(key) && key.className === unified_selection_1.TRANSIENT_ELEMENT_CLASSNAME)) { return keys; } const copy = new presentation_common_1.KeySet(); copy.add(keys, (key) => { // the callback is not going to be called with EntityProps as KeySet converts them // to InstanceKeys, but we want to keep the EntityProps case for correctness /* c8 ignore next 3 */ const isTransient = (presentation_common_1.Key.isInstanceKey(key) && key.className === unified_selection_1.TRANSIENT_ELEMENT_CLASSNAME) || (presentation_common_1.Key.isEntityProps(key) && key.classFullName === unified_selection_1.TRANSIENT_ELEMENT_CLASSNAME); return !isTransient; }); return copy; }; async function collect(iter) { const result = new Array(); for await (const value of iter) { result.push(value); } return result; } //# sourceMappingURL=PresentationManager.js.map