@itwin/tree-widget-react 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/lib/cjs/components/tree-header/TreeHeader.js +1 -1
  3. package/lib/cjs/components/tree-header/TreeHeader.js.map +1 -1
  4. package/lib/cjs/components/trees/categories-tree/CategoriesTreeButtons.js +3 -3
  5. package/lib/cjs/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  6. package/lib/cjs/components/trees/categories-tree/CategoriesVisibilityHandler.js +2 -1
  7. package/lib/cjs/components/trees/categories-tree/CategoriesVisibilityHandler.js.map +1 -1
  8. package/lib/cjs/components/trees/{models-tree/internal → common}/Tooltip.d.ts +7 -3
  9. package/lib/cjs/components/trees/{models-tree/internal → common}/Tooltip.js +5 -5
  10. package/lib/cjs/components/trees/common/Tooltip.js.map +1 -0
  11. package/lib/cjs/components/trees/common/UseActiveViewport.js.map +1 -1
  12. package/lib/cjs/components/trees/common/UseHierarchyVisibility.js +6 -3
  13. package/lib/cjs/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  14. package/lib/cjs/components/trees/common/Utils.js +1 -1
  15. package/lib/cjs/components/trees/common/Utils.js.map +1 -1
  16. package/lib/cjs/components/trees/common/components/TreeNodeCheckbox.js +5 -5
  17. package/lib/cjs/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  18. package/lib/cjs/components/trees/common/components/TreeRenderer.d.ts +1 -1
  19. package/lib/cjs/components/trees/common/components/TreeRenderer.js +4 -3
  20. package/lib/cjs/components/trees/common/components/TreeRenderer.js.map +1 -1
  21. package/lib/cjs/components/trees/common/components/TreeRenderer.scss +0 -6
  22. package/lib/cjs/components/trees/models-tree/ModelsTreeButtons.js +13 -13
  23. package/lib/cjs/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  24. package/lib/cjs/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  25. package/lib/cjs/components/trees/models-tree/ModelsTreeDefinition.js +1 -1
  26. package/lib/cjs/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  27. package/lib/cjs/components/trees/models-tree/UseModelsTree.d.ts +1 -1
  28. package/lib/cjs/components/trees/models-tree/UseModelsTree.js +23 -12
  29. package/lib/cjs/components/trees/models-tree/UseModelsTree.js.map +1 -1
  30. package/lib/cjs/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.d.ts +5 -0
  31. package/lib/cjs/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +28 -7
  32. package/lib/cjs/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  33. package/lib/cjs/components/trees/models-tree/internal/FilteredTree.d.ts +25 -0
  34. package/lib/cjs/components/trees/models-tree/internal/FilteredTree.js +178 -0
  35. package/lib/cjs/components/trees/models-tree/internal/FilteredTree.js.map +1 -0
  36. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +2 -1
  37. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeIdsCache.js +55 -23
  38. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  39. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeNode.js +1 -1
  40. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeNode.js.map +1 -1
  41. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.d.ts +8 -8
  42. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +162 -207
  43. package/lib/cjs/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  44. package/lib/cjs/components/trees/models-tree/internal/VisibilityChangeEventListener.d.ts +2 -0
  45. package/lib/cjs/components/trees/models-tree/internal/VisibilityChangeEventListener.js +11 -1
  46. package/lib/cjs/components/trees/models-tree/internal/VisibilityChangeEventListener.js.map +1 -1
  47. package/lib/esm/components/tree-header/TreeHeader.js +1 -1
  48. package/lib/esm/components/tree-header/TreeHeader.js.map +1 -1
  49. package/lib/esm/components/trees/categories-tree/CategoriesTreeButtons.js +3 -3
  50. package/lib/esm/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  51. package/lib/esm/components/trees/categories-tree/CategoriesVisibilityHandler.js +2 -1
  52. package/lib/esm/components/trees/categories-tree/CategoriesVisibilityHandler.js.map +1 -1
  53. package/lib/esm/components/trees/{models-tree/internal → common}/Tooltip.d.ts +7 -3
  54. package/lib/esm/components/trees/{models-tree/internal → common}/Tooltip.js +5 -5
  55. package/lib/esm/components/trees/common/Tooltip.js.map +1 -0
  56. package/lib/esm/components/trees/common/UseActiveViewport.js.map +1 -1
  57. package/lib/esm/components/trees/common/UseHierarchyVisibility.js +7 -4
  58. package/lib/esm/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  59. package/lib/esm/components/trees/common/Utils.js +1 -1
  60. package/lib/esm/components/trees/common/Utils.js.map +1 -1
  61. package/lib/esm/components/trees/common/components/TreeNodeCheckbox.js +6 -6
  62. package/lib/esm/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  63. package/lib/esm/components/trees/common/components/TreeRenderer.d.ts +1 -1
  64. package/lib/esm/components/trees/common/components/TreeRenderer.js +4 -3
  65. package/lib/esm/components/trees/common/components/TreeRenderer.js.map +1 -1
  66. package/lib/esm/components/trees/common/components/TreeRenderer.scss +0 -6
  67. package/lib/esm/components/trees/models-tree/ModelsTreeButtons.js +14 -14
  68. package/lib/esm/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  69. package/lib/esm/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  70. package/lib/esm/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  71. package/lib/esm/components/trees/models-tree/UseModelsTree.d.ts +1 -1
  72. package/lib/esm/components/trees/models-tree/UseModelsTree.js +24 -13
  73. package/lib/esm/components/trees/models-tree/UseModelsTree.js.map +1 -1
  74. package/lib/esm/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.d.ts +5 -0
  75. package/lib/esm/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +29 -8
  76. package/lib/esm/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  77. package/lib/esm/components/trees/models-tree/internal/FilteredTree.d.ts +25 -0
  78. package/lib/esm/components/trees/models-tree/internal/FilteredTree.js +173 -0
  79. package/lib/esm/components/trees/models-tree/internal/FilteredTree.js.map +1 -0
  80. package/lib/esm/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +2 -1
  81. package/lib/esm/components/trees/models-tree/internal/ModelsTreeIdsCache.js +55 -23
  82. package/lib/esm/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  83. package/lib/esm/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.d.ts +8 -8
  84. package/lib/esm/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +161 -206
  85. package/lib/esm/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  86. package/lib/esm/components/trees/models-tree/internal/VisibilityChangeEventListener.d.ts +2 -0
  87. package/lib/esm/components/trees/models-tree/internal/VisibilityChangeEventListener.js +11 -1
  88. package/lib/esm/components/trees/models-tree/internal/VisibilityChangeEventListener.js.map +1 -1
  89. package/lib/public/locales/en/TreeWidget.json +47 -43
  90. package/package.json +11 -11
  91. package/lib/cjs/components/trees/models-tree/internal/Tooltip.js.map +0 -1
  92. package/lib/esm/components/trees/models-tree/internal/Tooltip.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { BehaviorSubject, debounceTime, EMPTY, first, from, fromEventPattern, map, reduce, share, startWith, Subject, switchMap, takeUntil, tap } from "rxjs";
5
+ import { BehaviorSubject, debounceTime, EMPTY, filter, first, from, fromEventPattern, map, merge, reduce, scan, share, shareReplay, startWith, Subject, switchMap, take, takeUntil, tap, } from "rxjs";
6
6
  import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
7
7
  import { pushToMap } from "../../common/Utils";
8
8
  export const SET_CHANGE_DEBOUNCE_TIME = 20;
@@ -10,15 +10,32 @@ export class AlwaysAndNeverDrawnElementInfo {
10
10
  constructor(_viewport) {
11
11
  this._viewport = _viewport;
12
12
  this._disposeSubject = new Subject();
13
+ this._suppress = new Subject();
14
+ this._forceUpdate = new Subject();
13
15
  this._alwaysDrawn = this.createCacheEntryObservable({
14
16
  event: this._viewport.onAlwaysDrawnChanged,
15
17
  getSet: () => this._viewport.alwaysDrawn,
18
+ id: "alwaysDrawn",
16
19
  });
17
20
  this._neverDrawn = this.createCacheEntryObservable({
18
21
  event: this._viewport.onNeverDrawnChanged,
19
22
  getSet: () => this._viewport.neverDrawn,
23
+ id: "neverDrawn",
20
24
  });
21
- this._subscriptions = [this._alwaysDrawn.subscribe(), this._neverDrawn.subscribe()];
25
+ this._suppressors = this._suppress.pipe(scan((acc, suppress) => acc + (suppress ? 1 : -1), 0), startWith(0), shareReplay(1));
26
+ this._subscriptions = [
27
+ this._alwaysDrawn.subscribe(),
28
+ this._neverDrawn.subscribe(),
29
+ this._suppressors.pipe(filter((suppressors) => suppressors === 0)).subscribe({
30
+ next: () => this._forceUpdate.next(),
31
+ }),
32
+ ];
33
+ }
34
+ suppressChangeEvents() {
35
+ this._suppress.next(true);
36
+ }
37
+ resumeChangeEvents() {
38
+ this._suppress.next(false);
22
39
  }
23
40
  getElements({ setType, modelId, categoryId }) {
24
41
  const cache = setType === "always" ? this._alwaysDrawn : this._neverDrawn;
@@ -39,7 +56,7 @@ export class AlwaysAndNeverDrawnElementInfo {
39
56
  createCacheEntryObservable(props) {
40
57
  const event = props.event;
41
58
  const resultSubject = new BehaviorSubject(undefined);
42
- const obs = fromEventPattern((handler) => event.addListener(handler), (handler) => event.removeListener(handler)).pipe(
59
+ const obs = merge(fromEventPattern((handler) => event.addListener(handler), (handler) => event.removeListener(handler)), this._forceUpdate).pipe(
43
60
  // Fire the observable once at the beginning
44
61
  startWith(undefined),
45
62
  // Stop listening to events when dispose() is called
@@ -47,11 +64,13 @@ export class AlwaysAndNeverDrawnElementInfo {
47
64
  // Reset result subject as soon as a new event is emitted.
48
65
  // This will make newly subscribed observers wait for the debounce period to pass
49
66
  // instead of consuming the cached value which at this point becomes invalid.
50
- tap(() => resultSubject.next(undefined)), debounceTime(SET_CHANGE_DEBOUNCE_TIME),
67
+ tap(() => resultSubject.next(undefined)),
68
+ // Check if cache updates are not suppressed.
69
+ switchMap(() => this._suppressors.pipe(filter((suppressors) => suppressors === 0), take(1))), debounceTime(SET_CHANGE_DEBOUNCE_TIME),
51
70
  // Cancel pending request if dispose() is called.
52
71
  takeUntil(this._disposeSubject),
53
72
  // If multiple requests are sent at once, preserve only the result of the newest.
54
- switchMap(() => this.queryAlwaysOrNeverDrawnElementInfo(props.getSet())),
73
+ switchMap(() => this.queryAlwaysOrNeverDrawnElementInfo(props.getSet(), props.id)),
55
74
  // Share the result by using a subject which always emits the saved result.
56
75
  share({
57
76
  connector: () => resultSubject,
@@ -66,8 +85,8 @@ export class AlwaysAndNeverDrawnElementInfo {
66
85
  this._subscriptions = [];
67
86
  this._disposeSubject.next();
68
87
  }
69
- queryAlwaysOrNeverDrawnElementInfo(set) {
70
- const elementInfo = set?.size ? this.queryElementInfo([...set]) : EMPTY;
88
+ queryAlwaysOrNeverDrawnElementInfo(set, requestId) {
89
+ const elementInfo = set?.size ? this.queryElementInfo([...set], requestId) : EMPTY;
71
90
  return elementInfo.pipe(reduce((state, val) => {
72
91
  let entry = state.get(val.modelId);
73
92
  if (!entry) {
@@ -78,7 +97,7 @@ export class AlwaysAndNeverDrawnElementInfo {
78
97
  return state;
79
98
  }, new Map()));
80
99
  }
81
- queryElementInfo(elementIds) {
100
+ queryElementInfo(elementIds, requestId) {
82
101
  const executor = createECSqlQueryExecutor(this._viewport.iModel);
83
102
  const reader = executor.createQueryReader({
84
103
  ctes: [
@@ -110,6 +129,8 @@ export class AlwaysAndNeverDrawnElementInfo {
110
129
  WHERE parentId IS NULL
111
130
  `,
112
131
  bindings: [{ type: "idset", value: elementIds }],
132
+ }, {
133
+ restartToken: `ModelsTreeVisibilityHandler/${requestId}`,
113
134
  });
114
135
  return from(reader).pipe(map((row) => ({ elementId: row.elementId, modelId: row.modelId, categoryId: row.categoryId })));
115
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AlwaysAndNeverDrawnElementInfo.js","sourceRoot":"","sources":["../../../../../../src/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { BehaviorSubject, debounceTime, EMPTY, first, from, fromEventPattern, map, reduce, share, startWith, Subject, switchMap, takeUntil, tap } from \"rxjs\";\nimport { createECSqlQueryExecutor } from \"@itwin/presentation-core-interop\";\nimport { pushToMap } from \"../../common/Utils\";\n\nimport type { Observable, Subscription } from \"rxjs\";\nimport type { Viewport } from \"@itwin/core-frontend\";\nimport type { BeEvent, Id64Array, Id64Set, Id64String, IDisposable } from \"@itwin/core-bentley\";\n\ninterface ElementInfo {\n elementId: Id64String;\n modelId: Id64String;\n categoryId: Id64String;\n}\n\ntype CacheEntry = Map<Id64String, Map<Id64String, Id64Set>>;\n\nexport interface AlwaysOrNeverDrawnElementsQueryProps {\n modelId: Id64String;\n categoryId?: Id64String;\n}\n\nexport const SET_CHANGE_DEBOUNCE_TIME = 20;\n\nexport class AlwaysAndNeverDrawnElementInfo implements IDisposable {\n private _subscriptions: Subscription[];\n private _alwaysDrawn: Observable<CacheEntry>;\n private _neverDrawn: Observable<CacheEntry>;\n private _disposeSubject = new Subject<void>();\n\n constructor(private readonly _viewport: Viewport) {\n this._alwaysDrawn = this.createCacheEntryObservable({\n event: this._viewport.onAlwaysDrawnChanged,\n getSet: () => this._viewport.alwaysDrawn,\n });\n this._neverDrawn = this.createCacheEntryObservable({\n event: this._viewport.onNeverDrawnChanged,\n getSet: () => this._viewport.neverDrawn,\n });\n this._subscriptions = [this._alwaysDrawn.subscribe(), this._neverDrawn.subscribe()];\n }\n\n public getElements({ setType, modelId, categoryId }: { setType: \"always\" | \"never\" } & AlwaysOrNeverDrawnElementsQueryProps): Observable<Id64Set> {\n const cache = setType === \"always\" ? this._alwaysDrawn : this._neverDrawn;\n const getElements = categoryId\n ? (entry: CacheEntry | undefined) => {\n return entry?.get(modelId)?.get(categoryId) ?? new Set();\n }\n : (entry: CacheEntry | undefined) => {\n const modelEntry = entry?.get(modelId);\n const elements = new Set<Id64String>();\n for (const set of modelEntry?.values() ?? []) {\n set.forEach((id) => elements.add(id));\n }\n return elements;\n };\n\n return cache.pipe(map(getElements));\n }\n\n private createCacheEntryObservable(props: { event: BeEvent<() => void>; getSet(): Id64Set | undefined }) {\n const event = props.event;\n const resultSubject = new BehaviorSubject<CacheEntry | undefined>(undefined);\n const obs = fromEventPattern(\n (handler) => event.addListener(handler),\n (handler) => event.removeListener(handler),\n ).pipe(\n // Fire the observable once at the beginning\n startWith(undefined),\n // Stop listening to events when dispose() is called\n takeUntil(this._disposeSubject),\n // Reset result subject as soon as a new event is emitted.\n // This will make newly subscribed observers wait for the debounce period to pass\n // instead of consuming the cached value which at this point becomes invalid.\n tap(() => resultSubject.next(undefined)),\n debounceTime(SET_CHANGE_DEBOUNCE_TIME),\n // Cancel pending request if dispose() is called.\n takeUntil(this._disposeSubject),\n // If multiple requests are sent at once, preserve only the result of the newest.\n switchMap(() => this.queryAlwaysOrNeverDrawnElementInfo(props.getSet())),\n // Share the result by using a subject which always emits the saved result.\n share({\n connector: () => resultSubject,\n resetOnRefCountZero: false,\n }),\n // Wait until the result is available.\n first((x): x is CacheEntry => !!x),\n );\n return obs;\n }\n\n public dispose(): void {\n this._subscriptions.forEach((x) => x.unsubscribe());\n this._subscriptions = [];\n this._disposeSubject.next();\n }\n\n private queryAlwaysOrNeverDrawnElementInfo(set: Id64Set | undefined): Observable<CacheEntry> {\n const elementInfo = set?.size ? this.queryElementInfo([...set]) : EMPTY;\n return elementInfo.pipe(\n reduce((state, val) => {\n let entry = state.get(val.modelId);\n if (!entry) {\n entry = new Map();\n state.set(val.modelId, entry);\n }\n pushToMap(entry, val.categoryId, val.elementId);\n return state;\n }, new Map<Id64String, Map<Id64String, Id64Set>>()),\n );\n }\n\n private queryElementInfo(elementIds: Id64Array): Observable<ElementInfo> {\n const executor = createECSqlQueryExecutor(this._viewport.iModel);\n const reader = executor.createQueryReader({\n ctes: [\n `\n ElementInfo(elementId, modelId, categoryId, parentId) AS (\n SELECT\n ECInstanceId elementId,\n Model.Id modelId,\n Category.Id categoryId,\n Parent.Id parentId\n FROM bis.GeometricElement3d\n WHERE InVirtualSet(?, ECInstanceId)\n\n UNION ALL\n\n SELECT\n e.elementId,\n e.modelId,\n p.Category.Id categoryId,\n p.Parent.Id parentId\n FROM bis.GeometricElement3d p\n JOIN ElementInfo e ON p.ECInstanceId = e.parentId\n )\n `,\n ],\n ecsql: `\n SELECT elementId, modelId, categoryId\n FROM ElementInfo\n WHERE parentId IS NULL\n `,\n bindings: [{ type: \"idset\", value: elementIds }],\n });\n\n return from(reader).pipe(map((row) => ({ elementId: row.elementId, modelId: row.modelId, categoryId: row.categoryId })));\n }\n}\n"]}
1
+ {"version":3,"file":"AlwaysAndNeverDrawnElementInfo.js","sourceRoot":"","sources":["../../../../../../src/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport {\n BehaviorSubject,\n debounceTime,\n EMPTY,\n filter,\n first,\n from,\n fromEventPattern,\n map,\n merge,\n reduce,\n scan,\n share,\n shareReplay,\n startWith,\n Subject,\n switchMap,\n take,\n takeUntil,\n tap,\n} from \"rxjs\";\nimport { createECSqlQueryExecutor } from \"@itwin/presentation-core-interop\";\nimport { pushToMap } from \"../../common/Utils\";\n\nimport type { Observable, Subscription } from \"rxjs\";\nimport type { Viewport } from \"@itwin/core-frontend\";\nimport type { BeEvent, Id64Array, Id64Set, Id64String, IDisposable } from \"@itwin/core-bentley\";\n\ninterface ElementInfo {\n elementId: Id64String;\n modelId: Id64String;\n categoryId: Id64String;\n}\n\ntype CacheEntry = Map<Id64String, Map<Id64String, Id64Set>>;\n\nexport interface AlwaysOrNeverDrawnElementsQueryProps {\n modelId: Id64String;\n categoryId?: Id64String;\n}\n\nexport const SET_CHANGE_DEBOUNCE_TIME = 20;\n\nexport class AlwaysAndNeverDrawnElementInfo implements IDisposable {\n private _subscriptions: Subscription[];\n private _alwaysDrawn: Observable<CacheEntry>;\n private _neverDrawn: Observable<CacheEntry>;\n private _disposeSubject = new Subject<void>();\n\n private _suppressors: Observable<number>;\n private _suppress = new Subject<boolean>();\n private _forceUpdate = new Subject<void>();\n\n constructor(private readonly _viewport: Viewport) {\n this._alwaysDrawn = this.createCacheEntryObservable({\n event: this._viewport.onAlwaysDrawnChanged,\n getSet: () => this._viewport.alwaysDrawn,\n id: \"alwaysDrawn\",\n });\n this._neverDrawn = this.createCacheEntryObservable({\n event: this._viewport.onNeverDrawnChanged,\n getSet: () => this._viewport.neverDrawn,\n id: \"neverDrawn\",\n });\n this._suppressors = this._suppress.pipe(\n scan((acc, suppress) => acc + (suppress ? 1 : -1), 0),\n startWith(0),\n shareReplay(1),\n );\n this._subscriptions = [\n this._alwaysDrawn.subscribe(),\n this._neverDrawn.subscribe(),\n this._suppressors.pipe(filter((suppressors) => suppressors === 0)).subscribe({\n next: () => this._forceUpdate.next(),\n }),\n ];\n }\n\n public suppressChangeEvents() {\n this._suppress.next(true);\n }\n\n public resumeChangeEvents() {\n this._suppress.next(false);\n }\n\n public getElements({ setType, modelId, categoryId }: { setType: \"always\" | \"never\" } & AlwaysOrNeverDrawnElementsQueryProps): Observable<Id64Set> {\n const cache = setType === \"always\" ? this._alwaysDrawn : this._neverDrawn;\n const getElements = categoryId\n ? (entry: CacheEntry | undefined) => {\n return entry?.get(modelId)?.get(categoryId) ?? new Set();\n }\n : (entry: CacheEntry | undefined) => {\n const modelEntry = entry?.get(modelId);\n const elements = new Set<Id64String>();\n for (const set of modelEntry?.values() ?? []) {\n set.forEach((id) => elements.add(id));\n }\n return elements;\n };\n\n return cache.pipe(map(getElements));\n }\n\n private createCacheEntryObservable(props: { event: BeEvent<() => void>; getSet(): Id64Set | undefined; id: string }) {\n const event = props.event;\n const resultSubject = new BehaviorSubject<CacheEntry | undefined>(undefined);\n\n const obs = merge(\n fromEventPattern(\n (handler) => event.addListener(handler),\n (handler) => event.removeListener(handler),\n ),\n this._forceUpdate,\n ).pipe(\n // Fire the observable once at the beginning\n startWith(undefined),\n // Stop listening to events when dispose() is called\n takeUntil(this._disposeSubject),\n // Reset result subject as soon as a new event is emitted.\n // This will make newly subscribed observers wait for the debounce period to pass\n // instead of consuming the cached value which at this point becomes invalid.\n tap(() => resultSubject.next(undefined)),\n // Check if cache updates are not suppressed.\n switchMap(() =>\n this._suppressors.pipe(\n filter((suppressors) => suppressors === 0),\n take(1),\n ),\n ),\n debounceTime(SET_CHANGE_DEBOUNCE_TIME),\n // Cancel pending request if dispose() is called.\n takeUntil(this._disposeSubject),\n // If multiple requests are sent at once, preserve only the result of the newest.\n switchMap(() => this.queryAlwaysOrNeverDrawnElementInfo(props.getSet(), props.id)),\n // Share the result by using a subject which always emits the saved result.\n share({\n connector: () => resultSubject,\n resetOnRefCountZero: false,\n }),\n // Wait until the result is available.\n first((x): x is CacheEntry => !!x),\n );\n return obs;\n }\n\n public dispose(): void {\n this._subscriptions.forEach((x) => x.unsubscribe());\n this._subscriptions = [];\n this._disposeSubject.next();\n }\n\n private queryAlwaysOrNeverDrawnElementInfo(set: Id64Set | undefined, requestId: string): Observable<CacheEntry> {\n const elementInfo = set?.size ? this.queryElementInfo([...set], requestId) : EMPTY;\n return elementInfo.pipe(\n reduce((state, val) => {\n let entry = state.get(val.modelId);\n if (!entry) {\n entry = new Map();\n state.set(val.modelId, entry);\n }\n pushToMap(entry, val.categoryId, val.elementId);\n return state;\n }, new Map<Id64String, Map<Id64String, Id64Set>>()),\n );\n }\n\n private queryElementInfo(elementIds: Id64Array, requestId: string): Observable<ElementInfo> {\n const executor = createECSqlQueryExecutor(this._viewport.iModel);\n const reader = executor.createQueryReader(\n {\n ctes: [\n `\n ElementInfo(elementId, modelId, categoryId, parentId) AS (\n SELECT\n ECInstanceId elementId,\n Model.Id modelId,\n Category.Id categoryId,\n Parent.Id parentId\n FROM bis.GeometricElement3d\n WHERE InVirtualSet(?, ECInstanceId)\n\n UNION ALL\n\n SELECT\n e.elementId,\n e.modelId,\n p.Category.Id categoryId,\n p.Parent.Id parentId\n FROM bis.GeometricElement3d p\n JOIN ElementInfo e ON p.ECInstanceId = e.parentId\n )\n `,\n ],\n ecsql: `\n SELECT elementId, modelId, categoryId\n FROM ElementInfo\n WHERE parentId IS NULL\n `,\n bindings: [{ type: \"idset\", value: elementIds }],\n },\n {\n restartToken: `ModelsTreeVisibilityHandler/${requestId}`,\n },\n );\n\n return from(reader).pipe(map((row) => ({ elementId: row.elementId, modelId: row.modelId, categoryId: row.categoryId })));\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { HierarchyFilteringPath } from "@itwin/presentation-hierarchies";
2
+ import type { Id64String } from "@itwin/core-bentley";
3
+ import type { HierarchyNode } from "@itwin/presentation-hierarchies";
4
+ import type { ECClassHierarchyInspector } from "@itwin/presentation-shared";
5
+ export interface FilteredTree {
6
+ getVisibilityChangeTargets(node: HierarchyNode): VisibilityChangeTargets;
7
+ }
8
+ export declare const SUBJECT_CLASS_NAME: "BisCore.Subject";
9
+ export declare const MODEL_CLASS_NAME: "BisCore.GeometricModel3d";
10
+ export declare const CATEGORY_CLASS_NAME: "BisCore.SpatialCategory";
11
+ export declare const ELEMENT_CLASS_NAME: "BisCore.GeometricElement3d";
12
+ type CategoryKey = `${Id64String}-${Id64String}`;
13
+ export declare function parseCategoryKey(key: CategoryKey): {
14
+ modelId: string;
15
+ categoryId: string;
16
+ };
17
+ interface VisibilityChangeTargets {
18
+ subjects?: Set<Id64String>;
19
+ models?: Set<Id64String>;
20
+ categories?: Set<CategoryKey>;
21
+ elements?: Map<CategoryKey, Set<Id64String>>;
22
+ }
23
+ export declare function createFilteredTree(imodelAccess: ECClassHierarchyInspector, filteringPaths: HierarchyFilteringPath[]): Promise<FilteredTree>;
24
+ export {};
25
+ //# sourceMappingURL=FilteredTree.d.ts.map
@@ -0,0 +1,173 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { assert } from "@itwin/core-bentley";
6
+ import { HierarchyFilteringPath, HierarchyNodeIdentifier, HierarchyNodeKey } from "@itwin/presentation-hierarchies";
7
+ export const SUBJECT_CLASS_NAME = "BisCore.Subject";
8
+ export const MODEL_CLASS_NAME = "BisCore.GeometricModel3d";
9
+ export const CATEGORY_CLASS_NAME = "BisCore.SpatialCategory";
10
+ export const ELEMENT_CLASS_NAME = "BisCore.GeometricElement3d";
11
+ function createCategoryKey(modelId, categoryId) {
12
+ return `${modelId}-${categoryId}`;
13
+ }
14
+ export function parseCategoryKey(key) {
15
+ const [modelId, categoryId] = key.split("-");
16
+ return { modelId, categoryId };
17
+ }
18
+ export async function createFilteredTree(imodelAccess, filteringPaths) {
19
+ const root = {
20
+ children: new Map(),
21
+ };
22
+ for (const filteringPath of filteringPaths) {
23
+ const normalizedPath = HierarchyFilteringPath.normalize(filteringPath).path;
24
+ let parentNode = root;
25
+ for (let i = 0; i < normalizedPath.length; i++) {
26
+ if ("type" in parentNode && parentNode.isFilterTarget) {
27
+ break;
28
+ }
29
+ const identifier = normalizedPath[i];
30
+ if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(identifier)) {
31
+ break;
32
+ }
33
+ const currentNode = parentNode.children?.get(identifier.id);
34
+ if (currentNode !== undefined) {
35
+ parentNode = currentNode;
36
+ continue;
37
+ }
38
+ const type = await getType(imodelAccess, identifier.className);
39
+ const newNode = createFilteredTreeNode({
40
+ type,
41
+ id: identifier.id,
42
+ isFilterTarget: i === normalizedPath.length - 1,
43
+ parent: parentNode,
44
+ });
45
+ (parentNode.children ??= new Map()).set(identifier.id, newNode);
46
+ parentNode = newNode;
47
+ }
48
+ }
49
+ return {
50
+ getVisibilityChangeTargets: (node) => getVisibilityChangeTargets(root, node),
51
+ };
52
+ }
53
+ function getVisibilityChangeTargets(root, node) {
54
+ let lookupParents = [root];
55
+ const changeTargets = {};
56
+ const nodeKey = node.key;
57
+ if (!HierarchyNodeKey.isInstances(nodeKey)) {
58
+ return changeTargets;
59
+ }
60
+ // find the filtered parent nodes of the `node`
61
+ for (const parentKey of node.parentKeys) {
62
+ if (!HierarchyNodeKey.isInstances(parentKey)) {
63
+ continue;
64
+ }
65
+ // tree node might be merged from multiple instances. As filtered tree stores only one instance per node, we need to find all matching nodes
66
+ // and use them when checking for matching node in one level deeper.
67
+ const parentNodes = findMatchingFilteredNodes(lookupParents, parentKey.instanceKeys);
68
+ if (parentNodes.length === 0) {
69
+ return changeTargets;
70
+ }
71
+ lookupParents = parentNodes;
72
+ }
73
+ // find filtered nodes that match the `node`
74
+ const filteredNodes = findMatchingFilteredNodes(lookupParents, nodeKey.instanceKeys);
75
+ if (filteredNodes.length === 0) {
76
+ return changeTargets;
77
+ }
78
+ filteredNodes.forEach((filteredNode) => collectVisibilityChangeTargets(changeTargets, filteredNode));
79
+ return changeTargets;
80
+ }
81
+ function findMatchingFilteredNodes(lookupParents, keys) {
82
+ return lookupParents
83
+ .flatMap((lookup) => keys.map((key) => lookup.children?.get(key.id)))
84
+ .filter((lookupNode) => lookupNode !== undefined);
85
+ }
86
+ function collectVisibilityChangeTargets(changeTargets, node) {
87
+ if (node.isFilterTarget) {
88
+ addTarget(changeTargets, node);
89
+ return;
90
+ }
91
+ if (node.type === "element") {
92
+ // need to add parent ids as filter target will be an element
93
+ addTarget(changeTargets, node);
94
+ }
95
+ if (!node.children) {
96
+ return;
97
+ }
98
+ for (const child of node.children.values()) {
99
+ collectVisibilityChangeTargets(changeTargets, child);
100
+ }
101
+ }
102
+ function addTarget(filterTargets, node) {
103
+ switch (node.type) {
104
+ case "subject":
105
+ (filterTargets.subjects ??= new Set()).add(node.id);
106
+ return;
107
+ case "model":
108
+ (filterTargets.models ??= new Set()).add(node.id);
109
+ return;
110
+ case "category":
111
+ (filterTargets.categories ??= new Set()).add(createCategoryKey(node.modelId, node.id));
112
+ return;
113
+ case "element":
114
+ const categoryKey = createCategoryKey(node.modelId, node.categoryId);
115
+ const elements = (filterTargets.elements ??= new Map()).get(categoryKey);
116
+ if (elements) {
117
+ elements.add(node.id);
118
+ return;
119
+ }
120
+ filterTargets.elements.set(categoryKey, new Set([node.id]));
121
+ return;
122
+ }
123
+ }
124
+ function createFilteredTreeNode({ type, id, isFilterTarget, parent, }) {
125
+ if (type === "subject" || type === "model") {
126
+ return {
127
+ id,
128
+ isFilterTarget,
129
+ type,
130
+ };
131
+ }
132
+ if (type === "category") {
133
+ assert("type" in parent && parent.type === "model");
134
+ return {
135
+ id,
136
+ isFilterTarget,
137
+ type,
138
+ modelId: parent.id,
139
+ };
140
+ }
141
+ if ("type" in parent && parent.type === "category") {
142
+ return {
143
+ id,
144
+ isFilterTarget,
145
+ type,
146
+ modelId: parent.modelId,
147
+ categoryId: parent.id,
148
+ };
149
+ }
150
+ if ("type" in parent && parent.type === "element") {
151
+ return {
152
+ id,
153
+ isFilterTarget,
154
+ type,
155
+ modelId: parent.modelId,
156
+ categoryId: parent.categoryId,
157
+ };
158
+ }
159
+ throw new Error("Invalid parent node type");
160
+ }
161
+ async function getType(hierarchyChecker, className) {
162
+ if (await hierarchyChecker.classDerivesFrom(className, SUBJECT_CLASS_NAME)) {
163
+ return "subject";
164
+ }
165
+ if (await hierarchyChecker.classDerivesFrom(className, MODEL_CLASS_NAME)) {
166
+ return "model";
167
+ }
168
+ if (await hierarchyChecker.classDerivesFrom(className, CATEGORY_CLASS_NAME)) {
169
+ return "category";
170
+ }
171
+ return "element";
172
+ }
173
+ //# sourceMappingURL=FilteredTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilteredTree.js","sourceRoot":"","sources":["../../../../../../src/components/trees/models-tree/internal/FilteredTree.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { assert } from \"@itwin/core-bentley\";\nimport { HierarchyFilteringPath, HierarchyNodeIdentifier, HierarchyNodeKey } from \"@itwin/presentation-hierarchies\";\n\nimport type { Id64String } from \"@itwin/core-bentley\";\nimport type { HierarchyNode } from \"@itwin/presentation-hierarchies\";\nimport type { ECClassHierarchyInspector, InstanceKey } from \"@itwin/presentation-shared\";\n\ninterface FilteredTreeRootNode {\n children: Map<Id64String, FilteredTreeNode>;\n}\n\ninterface BaseFilteredTreeNode {\n id: string;\n children?: Map<Id64String, FilteredTreeNode>;\n isFilterTarget: boolean;\n}\n\ninterface GenericFilteredTreeNode extends BaseFilteredTreeNode {\n type: \"subject\" | \"model\";\n}\n\ninterface CategoryFilteredTreeNode extends BaseFilteredTreeNode {\n type: \"category\";\n modelId: Id64String;\n}\n\ninterface ElementFilteredTreeNode extends BaseFilteredTreeNode {\n type: \"element\";\n modelId: Id64String;\n categoryId: Id64String;\n}\n\ntype FilteredTreeNode = GenericFilteredTreeNode | CategoryFilteredTreeNode | ElementFilteredTreeNode;\n\nexport interface FilteredTree {\n getVisibilityChangeTargets(node: HierarchyNode): VisibilityChangeTargets;\n}\n\nexport const SUBJECT_CLASS_NAME = \"BisCore.Subject\" as const;\nexport const MODEL_CLASS_NAME = \"BisCore.GeometricModel3d\" as const;\nexport const CATEGORY_CLASS_NAME = \"BisCore.SpatialCategory\" as const;\nexport const ELEMENT_CLASS_NAME = \"BisCore.GeometricElement3d\" as const;\n\ntype CategoryKey = `${Id64String}-${Id64String}`;\n\nfunction createCategoryKey(modelId: string, categoryId: string): CategoryKey {\n return `${modelId}-${categoryId}`;\n}\n\nexport function parseCategoryKey(key: CategoryKey) {\n const [modelId, categoryId] = key.split(\"-\");\n return { modelId, categoryId };\n}\n\ninterface VisibilityChangeTargets {\n subjects?: Set<Id64String>;\n models?: Set<Id64String>;\n categories?: Set<CategoryKey>;\n elements?: Map<CategoryKey, Set<Id64String>>;\n}\n\nexport async function createFilteredTree(imodelAccess: ECClassHierarchyInspector, filteringPaths: HierarchyFilteringPath[]): Promise<FilteredTree> {\n const root: FilteredTreeRootNode = {\n children: new Map(),\n };\n\n for (const filteringPath of filteringPaths) {\n const normalizedPath = HierarchyFilteringPath.normalize(filteringPath).path;\n\n let parentNode: FilteredTreeRootNode | FilteredTreeNode = root;\n for (let i = 0; i < normalizedPath.length; i++) {\n if (\"type\" in parentNode && parentNode.isFilterTarget) {\n break;\n }\n\n const identifier = normalizedPath[i];\n if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(identifier)) {\n break;\n }\n\n const currentNode: FilteredTreeNode | undefined = parentNode.children?.get(identifier.id);\n if (currentNode !== undefined) {\n parentNode = currentNode;\n continue;\n }\n\n const type = await getType(imodelAccess, identifier.className);\n const newNode: FilteredTreeNode = createFilteredTreeNode({\n type,\n id: identifier.id,\n isFilterTarget: i === normalizedPath.length - 1,\n parent: parentNode as FilteredTreeNode,\n });\n (parentNode.children ??= new Map()).set(identifier.id, newNode);\n parentNode = newNode;\n }\n }\n\n return {\n getVisibilityChangeTargets: (node: HierarchyNode) => getVisibilityChangeTargets(root, node),\n };\n}\n\nfunction getVisibilityChangeTargets(root: FilteredTreeRootNode, node: HierarchyNode) {\n let lookupParents: Array<{ children?: Map<Id64String, FilteredTreeNode> }> = [root];\n const changeTargets: VisibilityChangeTargets = {};\n\n const nodeKey = node.key;\n if (!HierarchyNodeKey.isInstances(nodeKey)) {\n return changeTargets;\n }\n\n // find the filtered parent nodes of the `node`\n for (const parentKey of node.parentKeys) {\n if (!HierarchyNodeKey.isInstances(parentKey)) {\n continue;\n }\n\n // tree node might be merged from multiple instances. As filtered tree stores only one instance per node, we need to find all matching nodes\n // and use them when checking for matching node in one level deeper.\n const parentNodes = findMatchingFilteredNodes(lookupParents, parentKey.instanceKeys);\n if (parentNodes.length === 0) {\n return changeTargets;\n }\n lookupParents = parentNodes;\n }\n\n // find filtered nodes that match the `node`\n const filteredNodes = findMatchingFilteredNodes(lookupParents, nodeKey.instanceKeys);\n if (filteredNodes.length === 0) {\n return changeTargets;\n }\n\n filteredNodes.forEach((filteredNode) => collectVisibilityChangeTargets(changeTargets, filteredNode));\n return changeTargets;\n}\n\nfunction findMatchingFilteredNodes(lookupParents: Array<{ children?: Map<Id64String, FilteredTreeNode> }>, keys: InstanceKey[]) {\n return lookupParents\n .flatMap((lookup) => keys.map((key) => lookup.children?.get(key.id)))\n .filter((lookupNode): lookupNode is FilteredTreeNode => lookupNode !== undefined);\n}\n\nfunction collectVisibilityChangeTargets(changeTargets: VisibilityChangeTargets, node: FilteredTreeNode) {\n if (node.isFilterTarget) {\n addTarget(changeTargets, node);\n return;\n }\n\n if (node.type === \"element\") {\n // need to add parent ids as filter target will be an element\n addTarget(changeTargets, node);\n }\n\n if (!node.children) {\n return;\n }\n\n for (const child of node.children.values()) {\n collectVisibilityChangeTargets(changeTargets, child);\n }\n}\n\nfunction addTarget(filterTargets: VisibilityChangeTargets, node: FilteredTreeNode) {\n switch (node.type) {\n case \"subject\":\n (filterTargets.subjects ??= new Set()).add(node.id);\n return;\n case \"model\":\n (filterTargets.models ??= new Set()).add(node.id);\n return;\n case \"category\":\n (filterTargets.categories ??= new Set()).add(createCategoryKey(node.modelId, node.id));\n return;\n case \"element\":\n const categoryKey = createCategoryKey(node.modelId, node.categoryId);\n const elements = (filterTargets.elements ??= new Map()).get(categoryKey);\n if (elements) {\n elements.add(node.id);\n return;\n }\n filterTargets.elements.set(categoryKey, new Set([node.id]));\n return;\n }\n}\n\nfunction createFilteredTreeNode({\n type,\n id,\n isFilterTarget,\n parent,\n}: {\n type: FilteredTreeNode[\"type\"];\n id: string;\n isFilterTarget: boolean;\n parent: FilteredTreeNode | FilteredTreeRootNode;\n}): FilteredTreeNode {\n if (type === \"subject\" || type === \"model\") {\n return {\n id,\n isFilterTarget,\n type,\n };\n }\n\n if (type === \"category\") {\n assert(\"type\" in parent && parent.type === \"model\");\n return {\n id,\n isFilterTarget,\n type,\n modelId: parent.id,\n };\n }\n\n if (\"type\" in parent && parent.type === \"category\") {\n return {\n id,\n isFilterTarget,\n type,\n modelId: parent.modelId,\n categoryId: parent.id,\n };\n }\n\n if (\"type\" in parent && parent.type === \"element\") {\n return {\n id,\n isFilterTarget,\n type,\n modelId: parent.modelId,\n categoryId: parent.categoryId,\n };\n }\n\n throw new Error(\"Invalid parent node type\");\n}\n\nasync function getType(hierarchyChecker: ECClassHierarchyInspector, className: string) {\n if (await hierarchyChecker.classDerivesFrom(className, SUBJECT_CLASS_NAME)) {\n return \"subject\";\n }\n if (await hierarchyChecker.classDerivesFrom(className, MODEL_CLASS_NAME)) {\n return \"model\";\n }\n if (await hierarchyChecker.classDerivesFrom(className, CATEGORY_CLASS_NAME)) {\n return \"category\";\n }\n return \"element\";\n}\n"]}
@@ -11,6 +11,7 @@ export declare class ModelsTreeIdsCache {
11
11
  private _parentSubjectIds;
12
12
  private _modelInfos;
13
13
  constructor(_queryExecutor: LimitingECSqlQueryExecutor, _hierarchyConfig: ModelsTreeHierarchyConfiguration);
14
+ [Symbol.dispose](): void;
14
15
  private querySubjects;
15
16
  private queryModels;
16
17
  private getSubjectInfos;
@@ -36,7 +37,7 @@ export declare class ModelsTreeIdsCache {
36
37
  getModelCategories(modelId: Id64String): Promise<Id64Array>;
37
38
  getModelElementCount(modelId: Id64String): Promise<number>;
38
39
  getModelSubjects(modelId: Id64String): Promise<Id64Array>;
39
- private queryCategoryElementsCount;
40
+ private queryCategoryElementCounts;
40
41
  getCategoryElementsCount(modelId: Id64String, categoryId: Id64String): Promise<number>;
41
42
  getCategoryModels(categoryId: Id64String): Promise<Id64Array>;
42
43
  }
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
+ import { bufferTime, filter, firstValueFrom, mergeAll, mergeMap, ReplaySubject, Subject } from "rxjs";
5
6
  import { assert } from "@itwin/core-bentley";
6
7
  import { pushToMap } from "../../common/Utils";
7
8
  /** @internal */
@@ -9,7 +10,10 @@ export class ModelsTreeIdsCache {
9
10
  constructor(_queryExecutor, _hierarchyConfig) {
10
11
  this._queryExecutor = _queryExecutor;
11
12
  this._hierarchyConfig = _hierarchyConfig;
12
- this._categoryElementCounts = new Map();
13
+ this._categoryElementCounts = new ModelCategoryElementsCountCache(async (input) => this.queryCategoryElementCounts(input));
14
+ }
15
+ [Symbol.dispose]() {
16
+ this._categoryElementCounts[Symbol.dispose]();
13
17
  }
14
18
  async *querySubjects() {
15
19
  const subjectsQuery = `
@@ -258,44 +262,42 @@ export class ModelsTreeIdsCache {
258
262
  });
259
263
  return result;
260
264
  }
261
- async queryCategoryElementsCount(modelId, categoryId) {
265
+ async queryCategoryElementCounts(input) {
262
266
  const reader = this._queryExecutor.createQueryReader({
263
267
  ctes: [
264
268
  /* sql */ `
265
- CategoryElements(id) AS (
266
- SELECT ECInstanceId id
269
+ CategoryElements(id, modelId, categoryId) AS (
270
+ SELECT ECInstanceId, Model.Id, Category.Id
267
271
  FROM ${this._hierarchyConfig.elementClassSpecification}
268
272
  WHERE
269
- Model.Id = ?
270
- AND Category.Id = ?
271
- AND Parent.Id IS NULL
273
+ Parent.Id IS NULL
274
+ AND (
275
+ ${input.map(({ modelId, categoryId }) => `Model.Id = ${modelId} AND Category.Id = ${categoryId}`).join(" OR ")}
276
+ )
272
277
 
273
278
  UNION ALL
274
279
 
275
- SELECT c.ECInstanceId id
280
+ SELECT c.ECInstanceId, p.modelId, p.categoryId
276
281
  FROM ${this._hierarchyConfig.elementClassSpecification} c
277
282
  JOIN CategoryElements p ON c.Parent.Id = p.id
278
283
  )
279
284
  `,
280
285
  ],
281
- ecsql: `SELECT COUNT(*) FROM CategoryElements`,
282
- bindings: [
283
- { type: "id", value: modelId },
284
- { type: "id", value: categoryId },
285
- ],
286
- }, { rowFormat: "Indexes", limit: "unbounded" });
287
- return (await reader.next()).value[0];
288
- }
289
- async getCategoryElementsCount(modelId, categoryId) {
290
- const cacheKey = `${modelId}${categoryId}`;
291
- let result = this._categoryElementCounts.get(cacheKey);
292
- if (result !== undefined) {
293
- return result;
286
+ ecsql: `
287
+ SELECT modelId, categoryId, COUNT(id) elementsCount
288
+ FROM CategoryElements
289
+ GROUP BY modelId, categoryId
290
+ `,
291
+ }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" });
292
+ const result = new Array();
293
+ for await (const row of reader) {
294
+ result.push({ modelId: row.modelId, categoryId: row.categoryId, elementsCount: row.elementsCount });
294
295
  }
295
- result = await this.queryCategoryElementsCount(modelId, categoryId);
296
- this._categoryElementCounts.set(cacheKey, result);
297
296
  return result;
298
297
  }
298
+ async getCategoryElementsCount(modelId, categoryId) {
299
+ return this._categoryElementCounts.getCategoryElementsCount(modelId, categoryId);
300
+ }
299
301
  async getCategoryModels(categoryId) {
300
302
  const result = new Set();
301
303
  const modelInfos = await this.getModelInfos();
@@ -318,4 +320,34 @@ function forEachChildSubject(subjectInfos, parentSubject, cb) {
318
320
  forEachChildSubject(subjectInfos, childSubjectInfo, cb);
319
321
  });
320
322
  }
323
+ class ModelCategoryElementsCountCache {
324
+ constructor(_loader) {
325
+ this._loader = _loader;
326
+ this._cache = new Map();
327
+ this._requestsStream = new Subject();
328
+ this._subscription = this._requestsStream
329
+ .pipe(bufferTime(20), filter((requests) => requests.length > 0), mergeMap(async (requests) => this._loader(requests)), mergeAll())
330
+ .subscribe({
331
+ next: ({ modelId, categoryId, elementsCount }) => {
332
+ const subject = this._cache.get(`${modelId}${categoryId}`);
333
+ assert(!!subject);
334
+ subject.next(elementsCount);
335
+ },
336
+ });
337
+ }
338
+ [Symbol.dispose]() {
339
+ this._subscription.unsubscribe();
340
+ }
341
+ async getCategoryElementsCount(modelId, categoryId) {
342
+ const cacheKey = `${modelId}${categoryId}`;
343
+ let result = this._cache.get(cacheKey);
344
+ if (result !== undefined) {
345
+ return firstValueFrom(result);
346
+ }
347
+ result = new ReplaySubject(1);
348
+ this._cache.set(cacheKey, result);
349
+ this._requestsStream.next({ modelId, categoryId });
350
+ return firstValueFrom(result);
351
+ }
352
+ }
321
353
  //# sourceMappingURL=ModelsTreeIdsCache.js.map