UNPKG

@itwin/presentation-hierarchies-react

Version:

React components based on `@itwin/presentation-hierarchies`

248 lines 10.2 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.useTree = useTree; exports.useUnifiedSelectionTree = useUnifiedSelectionTree; const react_1 = require("react"); const presentation_hierarchies_1 = require("@itwin/presentation-hierarchies"); const TreeActions_js_1 = require("./internal/TreeActions.js"); const TreeModel_js_1 = require("./internal/TreeModel.js"); const UseUnifiedSelection_js_1 = require("./internal/UseUnifiedSelection.js"); const Utils_js_1 = require("./internal/Utils.js"); /** * A React hook that creates state for a tree component. * * The hook uses `@itwin/presentation-hierarchies` package to load the hierarchy data and returns a * component-agnostic result which may be used to render the hierarchy using any UI framework. * * See `README.md` for an example * * @see `useUnifiedSelectionTree` * @see `useIModelTree` * @public */ function useTree(props) { const { getTreeModelNode: _, ...rest } = useTreeInternal(props); return rest; } /** * A React hook that creates state for a tree component, that is integrated with unified selection * through the given selection storage (previously the storage was provided through the, now * deprecated, `UnifiedSelectionProvider`). * * The hook uses `@itwin/presentation-hierarchies` package to load the hierarchy data and returns a * component-agnostic result which may be used to render the hierarchy using any UI framework. * * See `README.md` for an example * * @see `useTree` * @see `useIModelUnifiedSelectionTree` * @see `UnifiedSelectionProvider` * @public */ function useUnifiedSelectionTree({ sourceName, selectionStorage, ...props }) { const { getTreeModelNode, ...rest } = useTreeInternal(props); return { ...rest, ...(0, UseUnifiedSelection_js_1.useUnifiedTreeSelection)({ sourceName, selectionStorage, getTreeModelNode }), }; } function useTreeInternal({ getHierarchyProvider, getFilteredPaths, onPerformanceMeasured, onHierarchyLimitExceeded, onHierarchyLoadError, }) { const [state, setState] = (0, react_1.useState)({ model: { idToNode: new Map(), parentChildMap: new Map(), rootNode: { id: undefined, nodeData: undefined } }, rootNodes: undefined, }); const onPerformanceMeasuredRef = useLatest(onPerformanceMeasured); const onHierarchyLimitExceededRef = useLatest(onHierarchyLimitExceeded); const onHierarchyLoadErrorRef = useLatest(onHierarchyLoadError); const [actions] = (0, react_1.useState)(() => new TreeActions_js_1.TreeActions((model) => { const rootNodes = model.parentChildMap.get(undefined) !== undefined ? generateTreeStructure(undefined, model) : undefined; setState({ model, rootNodes, }); }, (actionType, duration) => onPerformanceMeasuredRef.current?.(actionType, duration), (props) => onHierarchyLimitExceededRef.current?.(props), (props) => onHierarchyLoadErrorRef.current?.(props))); const currentFormatter = (0, react_1.useRef)(); const [hierarchyProvider, setHierarchyProvider] = (0, react_1.useState)(); (0, react_1.useEffect)(() => { const provider = getHierarchyProvider(); provider.setFormatter(currentFormatter.current); const removeHierarchyChangedListener = provider.hierarchyChanged.addListener((hierarchyChangeArgs) => { const shouldDiscardState = hierarchyChangeArgs?.filterChange?.newFilter !== undefined; actions.reloadTree({ state: shouldDiscardState ? "discard" : "keep" }); }); actions.setHierarchyProvider(provider); setHierarchyProvider(provider); return () => { removeHierarchyChangedListener(); actions.reset(); (0, Utils_js_1.safeDispose)(provider); }; }, [actions, getHierarchyProvider]); const [isFiltering, setIsFiltering] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { let disposed = false; const controller = new AbortController(); void (async () => { if (!hierarchyProvider) { return; } if (!getFilteredPaths) { hierarchyProvider.setHierarchyFilter(undefined); // reload tree in case hierarchy provider does not use hierarchy filter to load initial nodes actions.reloadTree({ state: "keep" }); setIsFiltering(false); return; } setIsFiltering(true); let paths; try { paths = await getFilteredPaths({ abortSignal: controller.signal }); } catch { } finally { if (!disposed) { hierarchyProvider.setHierarchyFilter(paths ? { paths } : undefined); setIsFiltering(false); } } })(); return () => { controller.abort(); disposed = true; }; }, [hierarchyProvider, getFilteredPaths, actions]); const getTreeModelNode = (0, react_1.useCallback)((nodeId) => { return actions.getNode(nodeId); }, [actions]); const getNode = (0, react_1.useCallback)((nodeId) => { const node = actions.getNode(nodeId); if (!node || !(0, TreeModel_js_1.isTreeModelHierarchyNode)(node)) { return undefined; } return createPresentationHierarchyNode(node, state.model); }, [actions, state.model]); const expandNode = (0, react_1.useCallback)((nodeId, isExpanded) => { actions.expandNode(nodeId, isExpanded); }, [actions]); const reloadTree = (0, react_1.useCallback)((options) => { actions.reloadTree(options); }, [actions]); const selectNodes = (0, react_1.useCallback)((nodeIds, changeType) => { actions.selectNodes(nodeIds, changeType); }, [actions]); const isNodeSelected = (0, react_1.useCallback)((nodeId) => TreeModel_js_1.TreeModel.isNodeSelected(state.model, nodeId), [state]); const setFormatter = (0, react_1.useCallback)((formatter) => { currentFormatter.current = formatter; /* c8 ignore next 3 */ if (!hierarchyProvider) { return; } hierarchyProvider.setFormatter(formatter); }, [hierarchyProvider]); const getHierarchyLevelDetails = (0, react_1.useCallback)((nodeId) => { const node = actions.getNode(nodeId); if (!hierarchyProvider || !node || (0, TreeModel_js_1.isTreeModelInfoNode)(node)) { return undefined; } const hierarchyNode = node.nodeData; if (hierarchyNode && presentation_hierarchies_1.HierarchyNode.isGroupingNode(hierarchyNode)) { return undefined; } return { hierarchyNode, getInstanceKeysIterator: (props) => hierarchyProvider.getNodeInstanceKeys({ parentNode: hierarchyNode, instanceFilter: props?.instanceFilter, hierarchyLevelSizeLimit: props?.hierarchyLevelSizeLimit, }), instanceFilter: node.instanceFilter, setInstanceFilter: (filter) => actions.setInstanceFilter(nodeId, filter), sizeLimit: node.hierarchyLimit, setSizeLimit: (value) => actions.setHierarchyLimit(nodeId, value), }; }, [actions, hierarchyProvider]); return { rootNodes: state.rootNodes, isLoading: !!state.model.rootNode.isLoading || isFiltering, expandNode, reloadTree, selectNodes, isNodeSelected, getTreeModelNode, getNode, getHierarchyLevelDetails, setFormatter, }; } function generateTreeStructure(parentNodeId, model) { const currentChildren = model.parentChildMap.get(parentNodeId); if (!currentChildren) { return undefined; } return currentChildren .map((childId) => model.idToNode.get(childId)) .filter((node) => !!node) .map((node) => { if ((0, TreeModel_js_1.isTreeModelHierarchyNode)(node)) { return createPresentationHierarchyNode(node, model); } if (node.type === "ResultSetTooLarge") { return { id: node.id, parentNodeId, type: node.type, resultSetSizeLimit: node.resultSetSizeLimit, }; } if (node.type === "NoFilterMatches") { return { id: node.id, parentNodeId, type: node.type, }; } return { id: node.id, parentNodeId, type: node.type, message: node.message, }; }); } function createPresentationHierarchyNode(modelNode, model) { let children; return { ...toPresentationHierarchyNodeBase(modelNode), get children() { if (!children) { children = generateTreeStructure(modelNode.id, model); } return children ? children : modelNode.children === true ? true : []; }, }; } function toPresentationHierarchyNodeBase(node) { return { id: node.id, label: node.label, nodeData: node.nodeData, isLoading: !!node.isLoading, isExpanded: !!node.isExpanded, isFilterable: !presentation_hierarchies_1.HierarchyNode.isGroupingNode(node.nodeData) && !!node.nodeData.supportsFiltering && node.children, isFiltered: !!node.instanceFilter, extendedData: node.nodeData.extendedData, }; } function useLatest(value) { const ref = (0, react_1.useRef)(value); (0, react_1.useEffect)(() => { ref.current = value; }, [value]); return ref; } //# sourceMappingURL=UseTree.js.map