Tree Widget React
npm install @itwin/tree-widget-reactCopyright © Bentley Systems, Incorporated. All rights reserved.
The @itwin/tree-widget-react package provides React components to build a widget with tree components' selector, along with all the building blocks that can be used individually.
The new 3.0 version of the package contains a few notable changes, compared to the previous 2.x generation.
- To allow easier customization of widget placement, the package now delivers a createTreeWidget() function that creates a tree widget definition, instead of a full UiItemsProvider implementation. See Usage section for details on how to use the new function.
- The underlying engine for building hierarchies has been changed from @itwin/presentation-components to @itwin/presentation-hierarchies-react. This is a significant change as the new library runs plain ECSQL queries and handles hierarchy creation on the frontend, as opposed to the previous version that relied on the backend to provide hierarchy data. This change allows this package to use more optimal queries and to be more flexible in terms of hierarchy creation. As a result, we're seeing 30-40% performance improvement for loading nodes in the Models tree when using a web backend and orders of magnitude improvement when using a local backend (desktop & mobile case).
This change adds a requirement for all tree components in this package to access iModels' metadata, which is achieved through a required getSchemaContext prop. See Creating schema context section for an example implementation of this function. This also adds an @itwin/ecschema-metadata peer dependency on version ^4.0.0.
In addition, the new tree components don't rely on the global selection manager provided by @itwin/presentation-frontend package. Instead, they require a unified selection storage object created using createStorage() function from @itwin/unified-selection package. See sections of individual tree components for how to supply it to them, and Creating unified selection storage section for an example for how to create the storage.
- The tree components delivered with the package have been updated to use the Tree component from @itwin/itwinui-react package instead of ControlledTree from @itwin/components-react. The new component is a little less dense, provides better accessibility and customization options.
| 2.x | 3.0 |
| ----------------------------------------------- | ----------------------------------------------- |
| !Tree widget 2.x | !Tree widget 3.0 |
This change introduces an @itwin/itwinui-react peer dependency on version ^3.11.0.
- The tree components now have hierarchy level size limiting and filtering features always turned on. The features were already available in 2.x versions, but were not enabled by default. See Hierarchy level size limiting and Hierarchy level filtering sections for more details.
- Behavior of header buttons like "Show all", "Hide all", etc. has been changed in filtered hierarchies' case. Previously they applied the visibility change on everything, no matter if the hierarchy is filtered or not. Now, when a hierarchy is filtered, only the nodes in the filtered hierarchy are affected.
- Models tree:
- The label filtering feature has been expanded to filter not only up to Models, but the whole hierarchy. This allows filtering the hierarchy to additionally find Category or Element nodes.
- Focus mode feature has been added to allow automatic hierarchy filtering as the application selection changes.
- In addition to the two filtering-related improvements above, we now allow displaying a subset of the tree by providing a getFilteredPaths function. See Displaying a subset of the tree section for more details.
- Display states' control has been modified to be hierarchy based. This means that changing display state of something deep in the hierarchy affects checkbox state of all its ancestors. And vice versa - changing display state of an ancestor affects all its descendants.
Typically, the package is used with an AppUI based application, but the building blocks may as well be used with any other iTwin.js React app.
In any case, before using any APIs or components delivered with the package, it needs to be initialized:
``tsx
import { TreeWidget } from "@itwin/tree-widget-react";
import { IModelApp } from "@itwin/core-frontend";
await TreeWidget.initialize(IModelApp.localization);
`
In AppUI based applications widgets are typically provided using UiItemsProvider implementations. The @itwin/tree-widget-react package delivers createTreeWidget function that can be used to add the tree widget to UI through a UiItemsProvider:
`tsx
import { UiItemsManager } from "@itwin/appui-react";
import { createTreeWidget, ModelsTreeComponent } from "@itwin/tree-widget-react";
UiItemsManager.register({
id: "tree-widget-provider",
getWidgets: () =>
[
createTreeWidget({
trees: [
// add a custom component
{ id: "my-tree-id", startIcon: , getLabel: () => "My Custom Tree", render: () => <>This is my custom tree.> },
// add the Models tree component delivered with the package
{
id: ModelsTreeComponent.id,
getLabel: () => ModelsTreeComponent.getLabel(),
render: (props) => (
getSchemaContext={getSchemaContext}
// see "Creating unified selection storage" section for example implementation
selectionStorage={unifiedSelectionStorage}
/>
),
},
],
}),
] as readonly Widget[],
});
`
As seen in the above code snippet, createTreeWidget takes a list of trees that are displayed in the widget. This package delivers a number of tree components for everyone's use (see below), but providing custom trees is also an option.
While we expect this package to be mostly used with AppUI and widget created through createTreeWidget, the package delivers components used within the widget to meet other use cases.
SelectableTree renders a tree selector and selected tree, based on the trees prop. Each tree definition contains a label, an optional icon and a render function that renders the component.
The component renders a tree that tries to replicate how a typical "Models" tree of the iModel would look like in the source application. There's also a header that renders models search box and various visibility control buttons.
Typical usage:
`tsx
import { ModelsTreeComponent } from "@itwin/tree-widget-react";
function MyWidget() {
return (
getSchemaContext={getSchemaContext}
// see "Creating unified selection storage" section for example implementation
selectionStorage={unifiedSelectionStorage}
headerButtons={[
(props) =>
(props) =>
]}
/>
);
}
`
Available header buttons:
- ModelsTreeComponent.ShowAllButton makes everything in the iModel displayed.ModelsTreeComponent.HideAllButton
- makes everything in the iModel hidden by turning off all models.ModelsTreeComponent.InvertButton
- inverts display of all models.ModelsTreeComponent.View2DButton
- toggles plan projection models' display.ModelsTreeComponent.View3DButton
- toggles non-plan projection models' display.ModelsTreeComponent.ToggleInstancesFocusButton
- enables/disables instances focusing mode.
#### Focus mode
The Models tree can be used in a "focus mode" where the tree is automatically filtered to show only elements that are selected in the application. The mode can be controlled through a toggle button in the component's header. Since the feature is mutually exclusive with the "search" feature, enabling it automatically disables the search functionality.
#### Custom models tree
This package provides building blocks for custom models tree:
- useModelsTree - hook for creating and managing models tree state.useModelsTreeButtonProps
- - hook for creating props for models tree buttons.
Example:
`tsx
import { useCallback } from "react";
import { TreeWithHeader, useModelsTree, useModelsTreeButtonProps, VisibilityTree, VisibilityTreeRenderer } from "@itwin/tree-widget-react";
import type { SelectionStorage } from "@itwin/unified-selection";
import type { IModelConnection, Viewport } from "@itwin/core-frontend";
import type { SchemaContext } from "@itwin/ecschema-metadata";
import type { ComponentPropsWithoutRef } from "react";
type VisibilityTreeRendererProps = ComponentPropsWithoutRef
type CustomModelsTreeRendererProps = Parameters
function CustomModelsTreeRenderer(props: CustomModelsTreeRendererProps) {
const getLabel = props.getLabel;
const getLabelCallback = useCallback
(node) => {
const originalLabel = getLabel(node);
return <>Custom node - {originalLabel}>;
},
[getLabel],
);
const getSublabelCallback = useCallback
return
}
interface CustomModelsTreeProps {
imodel: IModelConnection;
viewport: Viewport;
getSchemaContext: (imodel: IModelConnection) => SchemaContext;
selectionStorage: SelectionStorage;
}
function CustomModelsTreeComponent({ imodel, viewport, getSchemaContext, selectionStorage }: CustomModelsTreeProps) {
const { buttonProps } = useModelsTreeButtonProps({ imodel, viewport });
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport });
return (
]}
>
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
#### Displaying a subset of the tree
Models tree allows displaying a subset of all nodes by providing a getFilteredPaths or getSubTreePaths functions. These functions receive a helper function called createInstanceKeyPaths.getFilteredPaths
For this helper function can generate paths from either:
- a list of instance keys (targetItems)
- a label string
For getSubTreePaths this helper function can generate paths from:
- a list of instance keys (targetItems)
Based on the returned paths, the displayed hierarchy consists only of the targeted nodes, their ancestors, and their children.
Use getFilteredPaths when you need more control over filtering behaviour. Here are some example use cases:
- 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.
`tsx
type UseModelsTreeProps = Parameters
type GetFilteredPathsType = Exclude
function CustomModelsTreeComponentWithTargetItems({
viewport,
selectionStorage,
imodel,
targetItems,
}: {
viewport: Viewport;
selectionStorage: SelectionStorage;
imodel: IModelConnection;
targetItems: InstanceKey[];
}) {
const getFilteredPaths = useCallback
async ({ createInstanceKeyPaths }) => {
return createInstanceKeyPaths({
// list of instance keys representing nodes that should be displayed in the hierarchy
targetItems,
});
},
[targetItems],
);
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getFilteredPaths });
return (
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
- Post-process the paths created createInstanceKeyPaths: Use filter string to generate the paths, then apply additional filtering - e.g., remove paths that are too long.
`tsx
function CustomModelsTreeComponentWithPostProcessing({
viewport,
selectionStorage,
imodel,
}: {
viewport: Viewport;
selectionStorage: SelectionStorage;
imodel: IModelConnection;
}) {
const getFilteredPaths = useCallback
const defaultPaths = await createInstanceKeyPaths({ label: filter ?? "test" });
const result = new Array
for (const path of defaultPaths) {
const normalizedPath = HierarchyFilteringPath.normalize(path);
if (normalizedPath.path.length < 5) {
normalizedPath.options = { autoExpand: true };
result.push(normalizedPath);
}
}
return result;
}, []);
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getFilteredPaths });
return (
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
- Apply custom logic to generate instance keys: Generate instance keys using custom implementation. For example: only apply the given filter string to bis.Subject and bis.Model instances, but not others (bis.Category, bis.GeometricElement).
`tsxundefined
function CustomModelsTreeComponentWithFilterAndTargetItems({
viewport,
selectionStorage,
imodel,
filter,
}: {
viewport: Viewport;
selectionStorage: SelectionStorage;
imodel: IModelConnection;
filter: string | undefined;
}) {
const getFilteredPaths = useCallback
async ({ createInstanceKeyPaths, filter: activeFilter }) => {
if (!activeFilter) {
// if filter is not defined, return to avoid applying empty filter
return undefined;
}
const targetItems = new Array
for await (const row of imodel.createQueryReader(
SELECT ClassName, Id
FROM (
SELECT
ec_classname(e.ECClassId, 's.c') ClassName,
e.ECInstanceId Id,
COALESCE(e.UserLabel, e.CodeValue) Label
FROM BisCore.Subject e
UNION ALL
SELECT
ec_classname(m.ECClassId, 's.c') ClassName,
m.ECInstanceId Id,
COALESCE(e.UserLabel, e.CodeValue) Label
FROM BisCore.GeometricModel3d m
JOIN BisCore.Element e ON e.ECInstanceId = m.ModeledElement.Id
WHERE NOT m.IsPrivate
AND EXISTS (SELECT 1 FROM BisCore.Element WHERE Model.Id = m.ECInstanceId)
AND json_extract(e.JsonProperties, '$.PhysicalPartition.Model.Content') IS NULL
AND json_extract(e.JsonProperties, '$.GraphicalPartition3d.Model.Content') IS NULL
)
WHERE Label LIKE '%${activeFilter.replaceAll(/[%_\\]/g, "\\$&")}%' ESCAPE '\\'
,createInstanceKeyPaths
undefined,
{ rowFormat: QueryRowFormat.UseJsPropertyNames },
)) {
targetItems.push({ id: row.Id, className: row.ClassName });
}
// doesn't automatically set the autoExpand flag - set it here
const paths = await createInstanceKeyPaths({ targetItems });
return paths.map((path) => ({ ...path, options: { autoExpand: true } }));
},
[imodel],
);
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getFilteredPaths, filter });
return (
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
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:
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.
`tsx
type UseModelsTreeProps = Props
type GetSubTreePathsType = NonNullable
function CustomModelsTreeComponentWithTargetItems({
viewport,
selectionStorage,
imodel,
targetItems,
}: {
viewport: Viewport;
selectionStorage: SelectionStorage;
imodel: IModelConnection;
targetItems: InstanceKey[];
}) {
const getSubTreePaths = useCallback
async ({ createInstanceKeyPaths }) => {
return createInstanceKeyPaths({
// List of instance keys representing nodes that should be part of the hierarchy.
// Only these nodes, their ancestors and children will be part of that hierarchy.
targetItems,
});
},
[targetItems],
);
const { modelsTreeProps, rendererProps } = useModelsTree({ activeView: viewport, getSubTreePaths });
return (
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
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.
Typical usage:
`tsx
import { CategoriesTreeComponent } from "@itwin/tree-widget-react";
function MyWidget() {
return (
getSchemaContext={getSchemaContext}
// see "Creating unified selection storage" section for example implementation
selectionStorage={unifiedSelectionStorage}
headerButtons={[(props) =>
/>
);
}
`
Available header buttons:
- ModelsTreeComponent.ShowAllButton makes all categories and their subcategories displayed.ModelsTreeComponent.HideAllButton
- makes all categories hidden.ModelsTreeComponent.InvertButton
- inverts display of all categories.
#### Custom categories tree
This package provides building blocks for custom categories tree:
- useCategoriesTree - hook for creating and managing categories tree state.useCategoriesTreeButtonProps
- - hook for creating props for categories tree buttons.
Example:
`tsx
import { TreeWithHeader, useCategoriesTree, useCategoriesTreeButtonProps, VisibilityTree, VisibilityTreeRenderer } from "@itwin/tree-widget-react";
import type { IModelConnection, Viewport } from "@itwin/core-frontend";
import type { SelectionStorage } from "@itwin/unified-selection";
import type { SchemaContext } from "@itwin/ecschema-metadata";
import type { ComponentPropsWithoutRef } from "react";
type VisibilityTreeRendererProps = ComponentPropsWithoutRef
type CustomCategoriesTreeRendererProps = Parameters
function CustomCategoriesTreeRenderer(props: CustomCategoriesTreeRendererProps) {
const getLabel = props.getLabel;
const getLabelCallback = useCallback
(node) => {
const originalLabel = getLabel(node);
return <>Custom node - {originalLabel}>;
},
[getLabel],
);
const getSublabel = useCallback
return <>Custom sub label>;
}, []);
return
}
interface CustomCategoriesTreeProps {
imodel: IModelConnection;
viewport: Viewport;
getSchemaContext: (imodel: IModelConnection) => SchemaContext;
selectionStorage: SelectionStorage;
}
function CustomCategoriesTreeComponent({ imodel, viewport, getSchemaContext, selectionStorage }: CustomCategoriesTreeProps) {
const { buttonProps } = useCategoriesTreeButtonProps({ viewport });
const { categoriesTreeProps, rendererProps } = useCategoriesTree({ activeView: viewport, filter: "" });
return (
]}
>
getSchemaContext={getSchemaContext}
selectionStorage={selectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
}
`
The component renders a similar hierarchy to Models tree, but with the following changes:
- Only the hierarchy, without a header is rendered.
- Visibility control is not allowed.
- There's less hiding of Subject and Model nodes.
- Show not only geometric, but all Models and Elements.
In general, the component is expected to be used by advanced users to inspect contents of the iModel.
Typical usage:
`tsx
import { IModelContentTreeComponent } from "@itwin/tree-widget-react";
function MyWidget() {
return (
getSchemaContext={getSchemaContext}
// see "Creating unified selection storage" section for example implementation
selectionStorage={unifiedSelectionStorage}
/>
);
}
`
The package delivers a set of building blocks for creating trees that look and feel similar to the tree components provided by this package.
#### Custom basic tree
A "basic" tree is a tree that renders the hierarchy without visibility control - see iModel content tree for an example. Core components:
- Tree - component that manages tree state, selection and filtering.TreeRenderer
- - default renderer for tree data.
Example:
`tsx
import type { ComponentPropsWithoutRef } from "react";
import type { IModelConnection } from "@itwin/core-frontend";
import { Tree, TreeRenderer } from "@itwin/tree-widget-react";
import { createNodesQueryClauseFactory, createPredicateBasedHierarchyDefinition } from "@itwin/presentation-hierarchies";
import { createBisInstanceLabelSelectClauseFactory } from "@itwin/presentation-shared";
type TreeProps = ComponentPropsWithoutRef
const getHierarchyDefinition: TreeProps["getHierarchyDefinition"] = ({ imodelAccess }) => {
// create a hierarchy definition that defines what should be shown in the tree
// see https://github.com/iTwin/presentation/blob/master/packages/hierarchies/learning/imodel/HierarchyDefinition.md
const labelsQueryFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: imodelAccess });
const nodesQueryFactory = createNodesQueryClauseFactory({ imodelAccess, instanceLabelSelectClauseFactory: labelsQueryFactory });
return createPredicateBasedHierarchyDefinition({
classHierarchyInspector: imodelAccess,
hierarchy: {
// For root nodes, select all BisCore.GeometricModel3d instances
rootNodes: async () => [
{
fullClassName: "BisCore.GeometricModel3d",
query: {
ecsql:
SELECT
${await nodesQueryFactory.createSelectClause({
ecClassId: { selector: "this.ECClassId" },
ecInstanceId: { selector: "this.ECInstanceId" },
nodeLabel: {
selector: await labelsQueryFactory.createSelectClause({ classAlias: "this", className: "BisCore.GeometricModel3d" }),
},
})}
FROM BisCore.GeometricModel3d this
,
},
},
],
childNodes: [],
},
});
};
interface MyTreeProps {
imodel: IModelConnection;
}
function MyTree({ imodel }: MyTreeProps) {
return (
imodel={imodel}
selectionStorage={unifiedSelectionStorage}
getSchemaContext={getSchemaContext}
getHierarchyDefinition={getHierarchyDefinition}
treeRenderer={(props) =>
/>
);
}
`
#### Custom visibility tree
A visibility tree is a tree that renders the hierarchy and allows controlling visibility control through the use of "eye" checkboxes - see Models and Categories trees. Core components:
- VisibilityTree - same as Tree component but additionally manages visibility of instances represented by tree nodes.VisibilityTreeRenderer
- - same as TreeRenderer but additionally renders checkboxes for visibility control.
Example:
`tsx
import { BeEvent } from "@itwin/core-bentley";
import { VisibilityTree, VisibilityTreeRenderer } from "@itwin/tree-widget-react";
import { createNodesQueryClauseFactory, createPredicateBasedHierarchyDefinition } from "@itwin/presentation-hierarchies";
import { createBisInstanceLabelSelectClauseFactory } from "@itwin/presentation-shared";
import type { ComponentPropsWithoutRef } from "react";
import type { IModelConnection } from "@itwin/core-frontend";
type VisibilityTreeProps = ComponentPropsWithoutRef
const getHierarchyDefinition: VisibilityTreeProps["getHierarchyDefinition"] = ({ imodelAccess }) => {
// create a hierarchy definition that defines what should be shown in the tree
// see https://github.com/iTwin/presentation/blob/master/packages/hierarchies/learning/imodel/HierarchyDefinition.md
const labelsQueryFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: imodelAccess });
const nodesQueryFactory = createNodesQueryClauseFactory({ imodelAccess, instanceLabelSelectClauseFactory: labelsQueryFactory });
return createPredicateBasedHierarchyDefinition({
classHierarchyInspector: imodelAccess,
hierarchy: {
// For root nodes, select all BisCore.GeometricModel3d instances
rootNodes: async () => [
{
fullClassName: "BisCore.GeometricModel3d",
query: {
ecsql:
SELECT
${await nodesQueryFactory.createSelectClause({
ecClassId: { selector: "this.ECClassId" },
ecInstanceId: { selector: "this.ECInstanceId" },
nodeLabel: {
selector: await labelsQueryFactory.createSelectClause({ classAlias: "this", className: "BisCore.GeometricModel3d" }),
},
})}
FROM BisCore.GeometricModel3d this
,
},
},
],
childNodes: [],
},
});
};
const visibilityHandlerFactory: VisibilityTreeProps["visibilityHandlerFactory"] = () => {
return {
// event that can be used to notify tree when visibility of instances represented by tree nodes changes from outside.
onVisibilityChange: new BeEvent(),
async getVisibilityStatus(node: HierarchyNode): Promise
return { state: "visible" };
// determine visibility status of the instance represented by tree node.
},
async changeVisibility(node: HierarchyNode, on: boolean): Promise
// change visibility of the instance represented by tree node.
},
dispose() {
// if necessary, do some clean up before new visibility handler is created or component is unmounted.
},
};
};
interface MyVisibilityTreeProps {
imodel: IModelConnection;
}
function MyVisibilityTree({ imodel }: MyVisibilityTreeProps) {
return (
imodel={imodel}
selectionStorage={unifiedSelectionStorage}
getSchemaContext={getSchemaContext}
getHierarchyDefinition={getHierarchyDefinition}
visibilityHandlerFactory={visibilityHandlerFactory}
treeRenderer={(props) =>
/>
);
}
`
All tree components in this package enforce a hierarchy level size limit. This means that when a node is expanded, only a certain number of child nodes are loaded. The limit is enforced to prevent loading too many nodes at once and to keep the performance of the tree components at an acceptable level.
By default, the limit is set to 1000 nodes and components allow users to increase it to 10,000 for each hierarchy level individually:
!Hierarchy level size limit override example
All tree components in this package allow users to filter nodes at each hierarchy level. The filter is applied to a single hierarchy level, which allows users to reduce amount of nodes being loaded - this is especially useful when a hierarchy level size limit is hit:
!Hierarchy level filtering example
Tree components that support selection synchronization, require a unified selection storage object created using createStorage() function from @itwin/unified-selection package.
Typically, we want one unified selection storage per application - this makes sure that selection in all application's components is synchronized. Below is an example implementation of getUnifiedSelectionStorage function that creates the storage and clears it when an iModel is closed:
`tsx
import { IModelConnection } from "@itwin/core-frontend";
import { createStorage } from "@itwin/unified-selection";
import type { SelectionStorage } from "@itwin/unified-selection";
let unifiedSelectionStorage: SelectionStorage | undefined;
function getUnifiedSelectionStorage(): SelectionStorage {
if (!unifiedSelectionStorage) {
unifiedSelectionStorage = createStorage();
IModelConnection.onClose.addListener((imodel) => {
unifiedSelectionStorage!.clearStorage({ imodelKey: imodel.key });
});
}
return unifiedSelectionStorage;
}
`
In case the application is also using components driven by APIs from @itwin/presentation-frontend package, which has its own selection manager, the single unified selection storage object should be passed to initialize function, e.g.:
`tsx
import { Presentation } from "@itwin/presentation-frontend";
await Presentation.initialize({ selection: { selectionStorage: getUnifiedSelectionStorage() } });
`
All tree components delivered with the package require a SchemaContext to be able to access iModels metadata.
Typically, we want one schema context per iModel per application - this allows schema information to be shared across components, saving memory and time required to access the metadata. Below is an example implementation of getSchemaContext function, required by tree components:
`tsx
import { SchemaContext } from "@itwin/ecschema-metadata";
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";
import type { IModelConnection } from "@itwin/core-frontend";
const schemaContextCache = new Map
function getSchemaContext(imodel: IModelConnection) {
const key = imodel.getRpcProps().key;
let schemaContext = schemaContextCache.get(key);
if (!schemaContext) {
const schemaLocater = new ECSchemaRpcLocater(imodel.getRpcProps());
schemaContext = new SchemaContext();
schemaContext.addLocater(schemaLocater);
schemaContextCache.set(key, schemaContext);
imodel.onClose.addOnce(() => schemaContextCache.delete(key));
}
return schemaContext;
}
`
Note: Using ECSchemaRpcLocater requires the application to support ECSchemaRpcInterface. This means registering the interface and, on the backend, registering the implementation by calling ECSchemaRpcImpl.register().
Components from this package allows consumers to track performance of specific features.
This can be achieved by passing onPerformanceMeasured function to CategoriesTreeComponent, ModelsTreeComponent, IModelContentTreeComponent. The function is invoked with feature id and time elapsed as the component is being used. List of tracked features:
- "{tree}-initial-load" - time it takes to load initial nodes after the tree is created."{tree}-hierarchy-level-load"
- - time it takes to load child nodes when a node is expanded."{tree}-reload"
- - time it takes to reload the tree after data in the iModel changes or it's being reloaded due to filtering.
Where {tree} specifies which tree component the feature is of.
Components from this package allows consumers to track the usage of specific features.
This can be achieved by passing onFeatureUsed function to CategoriesTreeComponent, ModelsTreeComponent, IModelContentTreeComponent. The function is invoked with feature id as the component is being used. List of tracked features:
- "choose-{tree}" - when a tree is selected in the tree selector."use-{tree}"
- - when an interaction with a tree hierarchy happens. This includes any kind of interaction with nodes, including them being expanded/collapsed, selected, filtered, their visibility change, etc."{tree}-visibility-change"
- - when visibility is toggled using an "eye" button."{tree}-error-timeout"
- - when a request timeouts while loading hierarchy or filtering."{tree}-error-unknown"
- - when an unknown error occurs while loading hierarchy or filtering."models-tree-showall"
- - when "Show All" button is used in ModelsTreeComponent."models-tree-hideall"
- - when "Hide All" button is used in ModelsTreeComponent."models-tree-invert"
- - when "Invert" button is used in ModelsTreeComponent."models-tree-view2d"
- - when "Toggle 2D Views" button is used in ModelsTreeComponent."models-tree-view3d"
- - when "Toggle 3D Views" button is used in ModelsTreeComponent."models-tree-instancesfocus"
- - when "Instances focus mode" toggle button is used in ModelsTreeComponent."models-tree-zoom-to-node"
- - when node is zoomed to in ModelsTree."models-tree-filtering"
- - when a filter is applied in ModelsTree."models-tree-hierarchy-level-filtering"
- - when a hierarchy level filter is applied in the ModelsTree."models-tree-hierarchy-level-size-limit-hit"
- - when hierarchy level size limit is exceeded while loading nodes in the ModelsTree."categories-tree-showall"
- - when "Show All" button is used in CategoriesTreeComponent."categories-tree-hideall"
- - when "Hide All" button is used in CategoriesTreeComponent."categories-tree-invert"
- - when "Invert" button is used in CategoriesTreeComponent.
Where {tree} specifies which tree component the feature is of.
For individual tree components the callbacks should be supplied through props:
`tsx
import { IModelContentTreeComponent } from "@itwin/tree-widget-react";
function MyWidget() {
return (
console.log(TreeWidget [${feature}] took ${elapsedTime} ms);TreeWidget [${feature}] used
}}
onFeatureUsed={(feature) => {
console.log();`
}}
getSchemaContext={getSchemaContext}
selectionStorage={unifiedSelectionStorage}
/>
);
}
For custom tree components TelemetryContextProvider should be used:
`tsx
import { TelemetryContextProvider, useCategoriesTree, VisibilityTree, VisibilityTreeRenderer } from "@itwin/tree-widget-react";
function MyWidget() {
return (
onPerformanceMeasured={(feature, elapsedTime) => {
console.log(TreeWidget [${feature}] took ${elapsedTime} ms);TreeWidget [${feature}] used
}}
onFeatureUsed={(feature) => {
console.log();
}}
>
);
}
function MyTree() {
const { categoriesTreeProps, rendererProps } = useCategoriesTree({ activeView: viewport, filter: "" });
return (
// VisibilityTree will use provided telemetry context to report used features and their performance
getSchemaContext={getSchemaContext}
selectionStorage={unifiedSelectionStorage}
imodel={imodel}
treeRenderer={(props) =>
/>
);
// see "Custom trees" section for more example implementations
}
``