UNPKG

@itwin/unified-selection

Version:

Package for managing unified selection in iTwin.js applications.

345 lines 15.9 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.createHiliteSetProvider = createHiliteSetProvider; const rxjs_1 = require("rxjs"); const rxjs_for_await_1 = require("rxjs-for-await"); const presentation_shared_1 = require("@itwin/presentation-shared"); const Utils_js_1 = require("./Utils.js"); const HILITE_SET_EMIT_FREQUENCY = 20; /** * Creates a hilite set provider that returns a `HiliteSet` for given selectables. * @public */ function createHiliteSetProvider(props) { return new HiliteSetProviderImpl(props); } class HiliteSetProviderImpl { _imodelAccess; // Map between a class name and its type _classRelationCache; constructor(props) { this._imodelAccess = props.imodelAccess; this._classRelationCache = new Map(); } /** * Get hilite set iterator for supplied `Selectables`. */ getHiliteSet(props) { const obs = this.getHiliteSetObservable(props); return (0, rxjs_for_await_1.eachValueFrom)(obs); } /** * Returns a "hot" observable of hilite sets. */ getHiliteSetObservable({ selectables }) { const instancesByType = this.getInstancesByType(selectables); const observables = { models: this.getHilitedModels(instancesByType), subCategories: this.getHilitedSubCategories(instancesByType), elements: this.getHilitedElements(instancesByType), }; let hiliteSet = { models: [], subCategories: [], elements: [] }; let lastEmitTime = performance.now(); const subject = new rxjs_1.Subject(); const subscriptions = ["models", "subCategories", "elements"].map((key) => observables[key].subscribe({ next(val) { hiliteSet[key].push(val); if (performance.now() - lastEmitTime < HILITE_SET_EMIT_FREQUENCY) { return; } subject.next(hiliteSet); hiliteSet = { models: [], subCategories: [], elements: [] }; lastEmitTime = performance.now(); }, complete() { observables[key] = undefined; if (observables.models || observables.subCategories || observables.elements) { return; } // Emit last batch before completing the observable. if (hiliteSet.elements.length || hiliteSet.models.length || hiliteSet.subCategories.length) { subject.next(hiliteSet); } subject.complete(); }, error(err) { subscriptions.forEach((x) => x.unsubscribe()); subscriptions.length = 0; subject.error(err); }, })); return subject; } async getType(key) { const normalizedClassName = (0, presentation_shared_1.normalizeFullClassName)(key.className); const cachedType = this._classRelationCache.get(normalizedClassName); if (cachedType) { return cachedType; } const promise = this.getTypeImpl(normalizedClassName).then((res) => { // Update the cache with the result of the promise. this._classRelationCache.set(normalizedClassName, res); return res; }); // Add the promise to cache to prevent `getTypeImpl` being called multiple times. this._classRelationCache.set(normalizedClassName, promise); return promise; } async getTypeImpl(fullClassName) { return ((await this.checkType(fullClassName, "BisCore.Subject", "subject")) ?? (await this.checkType(fullClassName, "BisCore.Model", "model")) ?? (await this.checkType(fullClassName, "BisCore.Category", "category")) ?? (await this.checkType(fullClassName, "BisCore.SubCategory", "subCategory")) ?? (await this.checkType(fullClassName, "Functional.FunctionalElement", "functionalElement")) ?? (await this.checkType(fullClassName, "BisCore.GroupInformationElement", "groupInformationElement")) ?? (await this.checkType(fullClassName, "BisCore.GeometricElement", "geometricElement")) ?? (await this.checkType(fullClassName, "BisCore.Element", "element")) ?? "unknown"); } async checkType(keyClassName, checkClassName, type) { try { const res = this._imodelAccess.classDerivesFrom(keyClassName, checkClassName); const isOfType = typeof res === "boolean" ? res : await res; return isOfType ? type : undefined; } catch (e) { // we may be checking against a non-existing schema (e.g. Functional), in which case we should // return undefined instead of throwing an error if (e instanceof Error && e.message.match(/Schema "[\w\d_]+" not found/)) { return undefined; } throw e; } } getInstancesByType(selectables) { const keyTypeObs = (0, rxjs_1.merge)((0, rxjs_1.from)(selectables.custom.values()).pipe((0, rxjs_1.mergeMap)((selectable) => selectable.loadInstanceKeys())), (0, rxjs_1.from)(selectables.instanceKeys).pipe((0, rxjs_1.mergeMap)(([className, idSet]) => (0, rxjs_1.from)(idSet).pipe((0, rxjs_1.map)((id) => ({ className, id })))))).pipe((0, Utils_js_1.releaseMainThreadOnItemsCount)(500), // Get types for each instance key (0, rxjs_1.mergeMap)((instanceKey) => (0, rxjs_1.from)(this.getType(instanceKey)).pipe((0, rxjs_1.map)((instanceIdType) => ({ instanceId: instanceKey.id, instanceIdType })))), // Cache the results (0, rxjs_1.shareReplay)()); return Object.fromEntries(INSTANCE_TYPES.map((type) => [ type, keyTypeObs.pipe((0, rxjs_1.filter)(({ instanceIdType }) => instanceIdType === type), (0, rxjs_1.map)(({ instanceId }) => instanceId), unique()), ])); } getHilitedModels(instancesByType) { return (0, rxjs_1.forkJoin)({ modelKeys: instancesByType.model.pipe((0, rxjs_1.toArray)()), subjectKeys: instancesByType.subject.pipe((0, rxjs_1.toArray)()), }).pipe((0, rxjs_1.mergeMap)(({ modelKeys, subjectKeys }) => { if (!modelKeys.length && !subjectKeys.length) { return rxjs_1.EMPTY; } const bindings = []; const ctes = [ ` ChildSubjects(ECInstanceId, JsonProperties) AS ( SELECT ECInstanceId, JsonProperties FROM BisCore.Subject WHERE ${(0, Utils_js_1.formIdBindings)("ECInstanceId", subjectKeys, bindings)} UNION ALL SELECT r.ECInstanceId, r.JsonProperties FROM ChildSubjects s JOIN BisCore.Subject r ON r.Parent.Id = s.ECInstanceId ) `, ` Models(ECInstanceId) AS ( SELECT s.ECInstanceId AS ECInstanceId FROM BisCore.Model s WHERE ${(0, Utils_js_1.formIdBindings)("ECInstanceId", modelKeys, bindings)} ) `, ]; const ecsql = [ ` SELECT r.ECInstanceId AS ECInstanceId FROM ChildSubjects s JOIN BisCore.PhysicalPartition r ON r.Parent.Id = s.ECInstanceId OR json_extract(s.JsonProperties,'$.Subject.Model.TargetPartition') = printf('0x%x', r.ECInstanceId) `, ` SELECT ECInstanceId FROM Models `, ].join(" UNION "); return (0, rxjs_1.from)(executeQuery(this._imodelAccess, { ctes, ecsql, bindings })); })); } getHilitedSubCategories(instancesByType) { return (0, rxjs_1.forkJoin)({ subCategoryKeys: instancesByType.subCategory.pipe((0, rxjs_1.toArray)()), categoryKeys: instancesByType.category.pipe((0, rxjs_1.toArray)()), }).pipe((0, rxjs_1.mergeMap)(({ subCategoryKeys, categoryKeys }) => { if (!subCategoryKeys.length && !categoryKeys.length) { return rxjs_1.EMPTY; } const bindings = []; const ctes = [ ` CategorySubCategories(ECInstanceId) AS ( SELECT r.ECInstanceId AS ECInstanceId FROM BisCore.Category s JOIN BisCore.SubCategory r ON r.Parent.Id = s.ECInstanceId WHERE ${(0, Utils_js_1.formIdBindings)("s.ECInstanceId", categoryKeys, bindings)} ) `, ` SubCategories(ECInstanceId) AS ( SELECT s.ECInstanceId AS ECInstanceId FROM BisCore.SubCategory s WHERE ${(0, Utils_js_1.formIdBindings)("s.ECInstanceId", subCategoryKeys, bindings)} ) `, ]; const ecsql = [`SELECT ECInstanceId FROM CategorySubCategories`, `SELECT ECInstanceId FROM SubCategories`].join(" UNION "); return (0, rxjs_1.from)(executeQuery(this._imodelAccess, { ctes, ecsql, bindings })); })); } getHilitedElements(instancesByType) { return (0, rxjs_1.forkJoin)({ groupInformationElementKeys: instancesByType.groupInformationElement.pipe((0, rxjs_1.toArray)()), geometricElementKeys: instancesByType.geometricElement.pipe((0, rxjs_1.toArray)()), functionalElements: instancesByType.functionalElement.pipe((0, rxjs_1.toArray)()), elementKeys: instancesByType.element.pipe((0, rxjs_1.toArray)()), }).pipe((0, rxjs_1.mergeMap)(({ groupInformationElementKeys, geometricElementKeys, functionalElements, elementKeys }) => { const hasFunctionalElements = !!functionalElements.length; if (!groupInformationElementKeys.length && !geometricElementKeys.length && !elementKeys.length && !hasFunctionalElements) { return rxjs_1.EMPTY; } const bindings = []; const ctes = [ ...(hasFunctionalElements ? this.getHilitedFunctionalElementsQueryCTEs(functionalElements, bindings) : []), ` GroupMembers(ECInstanceId, ECClassId) AS ( SELECT TargetECInstanceId, TargetECClassId FROM BisCore.ElementGroupsMembers WHERE ${(0, Utils_js_1.formIdBindings)("SourceECInstanceId", groupInformationElementKeys, bindings)} ) `, ` GroupGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM GroupMembers UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM GroupGeometricElements s JOIN BisCore.Element r ON r.Parent.Id = s.ECInstanceId ) `, ` ElementGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM BisCore.Element WHERE ${(0, Utils_js_1.formIdBindings)("ECInstanceId", elementKeys, bindings)} UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM ElementGeometricElements s JOIN BisCore.Element r ON r.Parent.Id = s.ECInstanceId ) `, ` GeometricElementGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM BisCore.GeometricElement WHERE ${(0, Utils_js_1.formIdBindings)("ECInstanceId", geometricElementKeys, bindings)} UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM GeometricElementGeometricElements s JOIN BisCore.Element r ON r.Parent.Id = s.ECInstanceId ) `, ]; const ecsql = [ ...(hasFunctionalElements ? ["SELECT ECInstanceId FROM FunctionalElementChildGeometricElements WHERE ECClassId IS (BisCore.GeometricElement)"] : []), "SELECT ECInstanceId FROM GeometricElementGeometricElements WHERE ECClassId IS (BisCore.GeometricElement)", "SELECT ECInstanceId FROM GroupGeometricElements WHERE ECClassId IS (BisCore.GeometricElement)", "SELECT ECInstanceId FROM ElementGeometricElements WHERE ECClassId IS (BisCore.GeometricElement)", ].join(" UNION "); return (0, rxjs_1.from)(executeQuery(this._imodelAccess, { ctes, ecsql, bindings })); })); } getHilitedFunctionalElementsQueryCTEs(functionalElements, bindings) { return [ ` ChildFunctionalElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM Functional.FunctionalElement WHERE ${(0, Utils_js_1.formIdBindings)("ECInstanceId", functionalElements, bindings)} UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM ChildFunctionalElements s JOIN Functional.FunctionalElement r ON r.Parent.Id = s.ECInstanceId ) `, ` PhysicalElements(ECInstanceId, ECClassId) AS ( SELECT r.SourceECInstanceId, r.SourceECClassId FROM ChildFunctionalElements s JOIN Functional.PhysicalElementFulfillsFunction r ON r.TargetECInstanceId = s.ECInstanceId ) `, ` DrawingGraphicElements(ECInstanceId, ECClassId) AS ( SELECT r.SourceECInstanceId, r.SourceECClassId FROM ChildFunctionalElements s JOIN Functional.DrawingGraphicRepresentsFunctionalElement r ON r.TargetECInstanceId = s.ECInstanceId ) `, ` PhysicalElementGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM PhysicalElements UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM PhysicalElementGeometricElements s JOIN BisCore.Element r ON r.Parent.Id = s.ECInstanceId ) `, ` DrawingGraphicElementGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM DrawingGraphicElements UNION ALL SELECT r.ECInstanceId, r.ECClassId FROM DrawingGraphicElementGeometricElements s JOIN BisCore.Element r ON r.Parent.Id = s.ECInstanceId ) `, ` FunctionalElementChildGeometricElements(ECInstanceId, ECClassId) AS ( SELECT ECInstanceId, ECClassId FROM PhysicalElementGeometricElements UNION SELECT ECInstanceId, ECClassId FROM DrawingGraphicElementGeometricElements ) `, ]; } } const INSTANCE_TYPES = [ "subject", "model", "category", "subCategory", "functionalElement", "groupInformationElement", "geometricElement", "element", "unknown", ]; function unique() { return function (obs) { return obs.pipe((0, rxjs_1.scan)((acc, val) => { if (acc.set.has(val)) { delete acc.val; return acc; } acc.set.add(val); acc.val = val; return acc; }, { set: new Set() }), (0, rxjs_1.map)(({ val }) => val), (0, rxjs_1.filter)((x) => !!x)); }; } async function* executeQuery(queryExecutor, query) { yield* (0, Utils_js_1.genericExecuteQuery)(queryExecutor, query, (row) => row.ECInstanceId); } //# sourceMappingURL=HiliteSetProvider.js.map