Skip to content

Commit 04dbd95

Browse files
JonasDovgrigasp
andauthored
[Tree-widget]: Add learning snippet for getSubTreePaths (#1388)
* Add learning snippet * Add change file * Run prettier * Update apps/learning-snippets/src/test/tree-widget/SubTreePaths.test.tsx Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> * Update apps/learning-snippets/src/test/tree-widget/SubTreePaths.test.tsx Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> * Run check-extractions --------- Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>
1 parent 583279d commit 04dbd95

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
/* eslint-disable import/no-duplicates */
6+
/* eslint-disable @typescript-eslint/no-shadow */
7+
8+
import { expect } from "chai";
9+
import { useCallback } from "react";
10+
import sinon from "sinon";
11+
import { UiFramework } from "@itwin/appui-react";
12+
import { IModelApp } from "@itwin/core-frontend";
13+
import { useModelsTree, VisibilityTree, VisibilityTreeRenderer } from "@itwin/tree-widget-react";
14+
import { createStorage } from "@itwin/unified-selection";
15+
import { cleanup, render, waitFor } from "@testing-library/react";
16+
import { buildIModel, insertPhysicalElement, insertPhysicalModelWithPartition, insertSpatialCategory } from "../../utils/IModelUtils.js";
17+
import { initializeLearningSnippetsTests, terminateLearningSnippetsTests } from "../../utils/InitializationUtils.js";
18+
import { getSchemaContext, getTestViewer, mockGetBoundingClientRect, TreeWidgetTestUtils } from "../../utils/TreeWidgetTestUtils.js";
19+
20+
import type { SelectionStorage } from "@itwin/unified-selection";
21+
import type { IModelConnection, Viewport } from "@itwin/core-frontend";
22+
import type { InstanceKey } from "@itwin/presentation-common";
23+
import type { Props } from "@itwin/presentation-shared";
24+
25+
// __PUBLISH_EXTRACT_START__ TreeWidget.GetSubTreePathsComponentWithTargetItemsExample
26+
type UseModelsTreeProps = Props<typeof useModelsTree>;
27+
type GetSubTreePathsType = NonNullable<UseModelsTreeProps["getSubTreePaths"]>;
28+
29+
function CustomModelsTreeComponentWithTargetItems({
30+
viewport,
31+
selectionStorage,
32+
imodel,
33+
targetItems,
34+
}: {
35+
viewport: Viewport;
36+
selectionStorage: SelectionStorage;
37+
imodel: IModelConnection;
38+
targetItems: InstanceKey[];
39+
}) {
40+
const getSubTreePaths = useCallback<GetSubTreePathsType>(
41+
async ({ createInstanceKeyPaths }) => {
42+
return createInstanceKeyPaths({
43+
// List of instance keys representing nodes that should be part of the hierarchy.
44+
// Only these nodes, their ancestors and children will be part of that hierarchy.
45+
targetItems,
46+
});
47+
},
48+
[targetItems],
49+
);
50+
51+
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getSubTreePaths });
52+
53+
return (
54+
<VisibilityTree
55+
{...modelsTreeProps}
56+
getSchemaContext={getSchemaContext}
57+
selectionStorage={selectionStorage}
58+
imodel={imodel}
59+
treeRenderer={(props) => <VisibilityTreeRenderer {...props} {...rendererProps} />}
60+
/>
61+
);
62+
}
63+
// __PUBLISH_EXTRACT_END__
64+
65+
describe("Tree widget", () => {
66+
mockGetBoundingClientRect();
67+
describe("Learning snippets", () => {
68+
describe("Components", () => {
69+
describe("SubTree paths", () => {
70+
before(async function () {
71+
await initializeLearningSnippetsTests();
72+
await TreeWidgetTestUtils.initialize();
73+
});
74+
75+
after(async function () {
76+
await terminateLearningSnippetsTests();
77+
TreeWidgetTestUtils.terminate();
78+
});
79+
80+
afterEach(async () => {
81+
sinon.restore();
82+
});
83+
84+
it("renders custom models tree component with filtered paths using targetItems", async function () {
85+
const imodel = await buildIModel(this, async (builder) => {
86+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" });
87+
const physicalModel2 = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel 2" });
88+
const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" });
89+
insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id });
90+
const category2 = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory 2" });
91+
insertPhysicalElement({ builder, modelId: physicalModel2.id, categoryId: category2.id });
92+
return { physicalModel, physicalModel2 };
93+
});
94+
const testViewport = getTestViewer(imodel.imodel, true);
95+
const unifiedSelectionStorage = createStorage();
96+
sinon.stub(IModelApp.viewManager, "selectedView").get(() => testViewport);
97+
sinon.stub(UiFramework, "getIModelConnection").returns(imodel.imodel);
98+
99+
using _ = { [Symbol.dispose]: cleanup };
100+
const { getByText, queryByText } = render(
101+
<CustomModelsTreeComponentWithTargetItems
102+
selectionStorage={unifiedSelectionStorage}
103+
imodel={imodel.imodel}
104+
viewport={testViewport}
105+
targetItems={[imodel.physicalModel]}
106+
/>,
107+
);
108+
109+
await waitFor(() => {
110+
getByText("TestPhysicalModel");
111+
expect(queryByText("TestPhysicalModel 2")).to.be.null;
112+
});
113+
});
114+
});
115+
});
116+
});
117+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Add learning snippet for `getSubTreePaths`",
4+
"packageName": "@itwin/tree-widget-react",
5+
"email": "100586436+JonasDov@users.noreply.github.com",
6+
"dependentChangeType": "none"
7+
}

packages/itwin/tree-widget/README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,19 @@ function CustomModelsTreeComponent({ imodel, viewport, getSchemaContext, selecti
218218

219219
#### Displaying a subset of the tree
220220

221-
Models tree allows displaying a subset of all nodes by providing a `getFilteredPaths` function. This function receives a helper function called `createInstanceKeyPaths`, which can generate paths from either:
221+
Models tree allows displaying a subset of all nodes by providing a `getFilteredPaths` or `getSubTreePaths` functions. These functions receive a helper function called `createInstanceKeyPaths`.
222+
For `getFilteredPaths` this helper function can generate paths from either:
222223

223224
- a list of instance keys (`targetItems`)
224225
- a label string
225226

227+
For `getSubTreePaths` this helper function can generate paths from:
228+
229+
- a list of instance keys (`targetItems`)
230+
226231
Based on the returned paths, the displayed hierarchy consists only of the targeted nodes, their ancestors, and their children.
227232

228-
Use `getFilteredPaths` when you need more control over which nodes are shown. Here are some example use cases:
233+
Use `getFilteredPaths` when you need more control over filtering behaviour. Here are some example use cases:
229234

230235
- **Filter by known instance keys**: You already have a list of `InstanceKey` items that should remain in the tree. Pass them as `targetItems` to `createInstanceKeyPaths`.
231236
<!-- [[include: [TreeWidget.GetFilteredPathsComponentWithTargetItemsExample], tsx]] -->
@@ -362,6 +367,55 @@ Use `getFilteredPaths` when you need more control over which nodes are shown. He
362367
```
363368
<!-- END EXTRACTION -->
364369

370+
Use `getSubTreePaths` when you need to restrict the visible hierarchy to a specific sub-tree of nodes, without changing how filtering works. Here is an example use case:
371+
372+
**Restrict the hierarchy to a sub-tree and keep the default filtering logic**: You already have a list of `InstanceKey` items that should remain in the tree. Pass them as `targetItems` to `createInstanceKeyPaths`. This will restrict the hierarchy to a sub-tree, but filtering will work as before.
373+
374+
<!-- [[include: [TreeWidget.GetSubTreePathsComponentWithTargetItemsExample], tsx]] -->
375+
<!-- BEGIN EXTRACTION -->
376+
377+
```tsx
378+
type UseModelsTreeProps = Props<typeof useModelsTree>;
379+
type GetSubTreePathsType = NonNullable<UseModelsTreeProps["getSubTreePaths"]>;
380+
381+
function CustomModelsTreeComponentWithTargetItems({
382+
viewport,
383+
selectionStorage,
384+
imodel,
385+
targetItems,
386+
}: {
387+
viewport: Viewport;
388+
selectionStorage: SelectionStorage;
389+
imodel: IModelConnection;
390+
targetItems: InstanceKey[];
391+
}) {
392+
const getSubTreePaths = useCallback<GetSubTreePathsType>(
393+
async ({ createInstanceKeyPaths }) => {
394+
return createInstanceKeyPaths({
395+
// List of instance keys representing nodes that should be part of the hierarchy.
396+
// Only these nodes, their ancestors and children will be part of that hierarchy.
397+
targetItems,
398+
});
399+
},
400+
[targetItems],
401+
);
402+
403+
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getSubTreePaths });
404+
405+
return (
406+
<VisibilityTree
407+
{...modelsTreeProps}
408+
getSchemaContext={getSchemaContext}
409+
selectionStorage={selectionStorage}
410+
imodel={imodel}
411+
treeRenderer={(props) => <VisibilityTreeRenderer {...props} {...rendererProps} />}
412+
/>
413+
);
414+
}
415+
```
416+
417+
<!-- END EXTRACTION -->
418+
365419
### Categories tree
366420

367421
The component, based on the active view, renders a hierarchy of either spatial (3d) or drawing (2d) categories. The hierarchy consists of two levels - the category (spatial or drawing) and its sub-categories. There's also a header that renders categories search box and various visibility control buttons.

0 commit comments

Comments
 (0)