UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

202 lines 9.09 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-deprecated */ /** @packageDocumentation * @module Tree */ import "../../common/DisposePolyfill.js"; import { useCallback } from "react"; import { Subject, takeUntil, tap } from "rxjs"; import { TreeEventHandler, } from "@itwin/components-react"; import { Guid } from "@itwin/core-bentley"; import { useDisposable } from "@itwin/core-react"; import { NodeKey } from "@itwin/presentation-common"; import { Presentation, SelectionChangeType, SelectionHelper } from "@itwin/presentation-frontend"; import { isPresentationTreeNodeItem } from "../PresentationTreeNodeItem.js"; import { toRxjsObservable } from "../Utils.js"; /** * Tree event handler that handles unified selection. * Extends wrapped tree event handler's functionality by adding, removing or replacing nodes in * unified selection. It also reacts to unified selection changes and selects/deselects tree nodes * according changes. * * **Note:** conditions used to determine if node is selected and nodes that should be added to * unified selection can be controlled by overriding 'shouldSelectNode' and 'createKeysForSelection' methods. * * @public * @deprecated in 5.7. All tree-related APIs have been deprecated in favor of the new generation hierarchy * building APIs (see https://github.com/iTwin/presentation/blob/33e79ee8d77f30580a9bab81a72884bda008db25/README.md#the-packages). */ export class UnifiedSelectionTreeEventHandler extends TreeEventHandler { #dataProvider; #selectionSourceName; #listeners = []; #cancelled = new Subject(); constructor(params) { super({ ...params, modelSource: params.nodeLoader.modelSource, }); this.#dataProvider = params.nodeLoader.dataProvider; this.#selectionSourceName = params.name ?? `Tree_${this.#dataProvider.rulesetId}_${Guid.createValue()}`; this.#listeners.push(Presentation.selection.selectionChange.addListener((args) => this.onSelectionChanged(args))); this.#listeners.push(this.modelSource.onModelChanged.addListener((args) => this.selectNodes(args[1]))); this.selectNodes(); } #dispose() { super.dispose(); this.#cancelled.next(); this.#listeners.forEach((unregister) => unregister()); } /** Disposes this event handler */ [Symbol.dispose]() { this.#dispose(); } /** @deprecated in 5.7. Use `[Symbol.dispose]` instead. */ dispose() { this.#dispose(); } onSelectionModified({ modifications }) { const withUnifiedSelection = toRxjsObservable(modifications).pipe(takeUntil(this.#cancelled), tap({ next: ({ selectedNodeItems, deselectedNodeItems }) => { if (selectedNodeItems.length !== 0) { this.addToSelection(selectedNodeItems); } if (deselectedNodeItems.length !== 0) { this.removeFromSelection(deselectedNodeItems); } }, })); return super.onSelectionModified({ modifications: withUnifiedSelection }); } onSelectionReplaced({ replacements }) { this.#cancelled.next(); let firstEmission = true; const withUnifiedSelection = toRxjsObservable(replacements).pipe(takeUntil(this.#cancelled), tap({ next: ({ selectedNodeItems }) => { if (selectedNodeItems.length === 0) { return; } if (firstEmission) { firstEmission = false; this.replaceSelection(selectedNodeItems); return; } this.addToSelection(selectedNodeItems); }, })); return super.onSelectionReplaced({ replacements: withUnifiedSelection }); } selectNodes(modelChange) { // when handling model change event only need to update newly added nodes if (modelChange) { this.updateAffectedNodes(modelChange); } else { this.updateAllNodes(); } } /** @deprecated in 4.0. Use [[isPresentationTreeNodeItem]] and [[PresentationTreeNodeItem.key]] to get [NodeKey]($presentation-common). */ /* c8 ignore start */ getNodeKey(node) { return this.#dataProvider.getNodeKey(node); } /* c8 ignore end */ /** * Determines if node should be selected. * Default implementation returns true if node key is in selection * or node is ECInstance node and instance key is in selection. */ shouldSelectNode(node, selection) { /* c8 ignore next 3 */ if (!isPresentationTreeNodeItem(node)) { return false; } // consider node selected if it's key is in selection if (selection.has(node.key)) { return true; } // ... or if it's an ECInstances node and any of instance keys is in selection if (NodeKey.isInstancesNodeKey(node.key) && node.key.instanceKeys.some((instanceKey) => selection.has(instanceKey))) { return true; } return false; } /** * Returns node keys that should be added, removed or used to replace unified selection. * Default implementation returns keys of supplied nodes. */ createKeysForSelection(nodes, _selectionType) { return this.getKeys(nodes); } getKeys(nodes) { const nodeKeys = nodes .map((node) => (isPresentationTreeNodeItem(node) ? node.key : /* c8 ignore next */ undefined)) .filter((key) => key !== undefined); return SelectionHelper.getKeysForSelection(nodeKeys); } addToSelection(nodes) { Presentation.selection.addToSelection(this.#selectionSourceName, this.#dataProvider.imodel, this.createKeysForSelection(nodes, SelectionChangeType.Add), 0, this.#dataProvider.rulesetId); } removeFromSelection(nodes) { Presentation.selection.removeFromSelection(this.#selectionSourceName, this.#dataProvider.imodel, this.createKeysForSelection(nodes, SelectionChangeType.Remove), 0, this.#dataProvider.rulesetId); } replaceSelection(nodes) { Presentation.selection.replaceSelection(this.#selectionSourceName, this.#dataProvider.imodel, this.createKeysForSelection(nodes, SelectionChangeType.Replace), 0, this.#dataProvider.rulesetId); } onSelectionChanged(evt) { if (evt.imodel !== this.#dataProvider.imodel) { return; } if (evt.source !== this.#selectionSourceName && (evt.changeType === SelectionChangeType.Clear || evt.changeType === SelectionChangeType.Replace)) { this.#cancelled.next(); } this.selectNodes(); } updateAllNodes() { const selection = Presentation.selection.getSelection(this.#dataProvider.imodel); this.modelSource.modifyModel((model) => { for (const node of model.iterateTreeModelNodes()) { this.updateNodeSelectionState(node, selection); } }); } updateAffectedNodes(modelChange) { const affectedNodeIds = [...modelChange.addedNodeIds, ...modelChange.modifiedNodeIds]; if (affectedNodeIds.length === 0) { return; } const selection = Presentation.selection.getSelection(this.#dataProvider.imodel); this.modelSource.modifyModel((model) => { for (const nodeId of affectedNodeIds) { const node = model.getNode(nodeId); /* c8 ignore next 3 */ if (!node) { continue; } this.updateNodeSelectionState(node, selection); } }); } updateNodeSelectionState(node, selection) { const shouldBeSelected = this.shouldSelectNode(node.item, selection); if (!node.isSelected && shouldBeSelected) { node.isSelected = true; } else if (node.isSelected && !shouldBeSelected) { node.isSelected = false; } } } /** * A custom hook which creates and disposes [[UnifiedSelectionTreeEventHandler]] * @public * @deprecated in 4.x. This hook is not compatible with React 18 `StrictMode`. Use [[usePresentationTreeState]] and * [[UsePresentationTreeProps.eventHandlerFactory]] instead or manually create and dispose [[UnifiedSelectionTreeEventHandler]]. */ export function useUnifiedSelectionTreeEventHandler(props) { return useDisposable(useCallback(() => new UnifiedSelectionTreeEventHandler(props), Object.values(props) /* eslint-disable-line react-hooks/exhaustive-deps */ /* want to re-create the handler whenever any prop changes */)); } //# sourceMappingURL=UseUnifiedSelection.js.map