UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

175 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstanceFilterBuilder = InstanceFilterBuilder; exports.usePresentationInstanceFilteringProps = usePresentationInstanceFilteringProps; const jsx_runtime_1 = require("react/jsx-runtime"); /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module InstancesFilter */ require("./InstanceFilterBuilder.scss"); const react_1 = require("react"); const rxjs_1 = require("rxjs"); const map_1 = require("rxjs/internal/operators/map"); const switchAll_1 = require("rxjs/internal/operators/switchAll"); const components_react_1 = require("@itwin/components-react"); const itwinui_react_1 = require("@itwin/itwinui-react"); const Utils_js_1 = require("../common/Utils.js"); const ECMetadataProvider_js_1 = require("./ECMetadataProvider.js"); const PresentationFilterBuilder_js_1 = require("./PresentationFilterBuilder.js"); const Utils_js_2 = require("./Utils.js"); /** * Component for building complex instance filters based on instance properties. In addition to filter builder component * it renders selector for classes that can be used to filter out available properties in filter rules. * @internal */ function InstanceFilterBuilder(props) { const { selectedClasses, classes, onSelectedClassesChanged, imodel, descriptor, descriptorInputKeys, ...restProps } = props; const [showClassSelectionWarning, setShowClassSelectionWarning] = (0, react_1.useState)(false); const options = (0, react_1.useMemo)(() => classes.map(createOption), [classes]); const selectedOptions = (0, react_1.useMemo)(() => selectedClasses.map((classInfo) => classInfo.id), [selectedClasses]); return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showClassSelectionWarning && ((0, jsx_runtime_1.jsx)(itwinui_react_1.Alert, { type: "warning", className: "class-selection-warning", children: (0, Utils_js_1.translate)("instance-filter-builder.class-selection-warning") })), (0, jsx_runtime_1.jsxs)("div", { className: "presentation-instance-filter", children: [(0, jsx_runtime_1.jsx)(itwinui_react_1.ComboBox, { enableVirtualization: true, multiple: true, options: options, value: selectedOptions, inputProps: { placeholder: selectedClasses.length ? (0, Utils_js_1.translate)("instance-filter-builder.selected-classes") : (0, Utils_js_1.translate)("instance-filter-builder.select-classes-optional"), }, onShow: () => setShowClassSelectionWarning(!!selectedOptions.length && (0, Utils_js_2.isFilterNonEmpty)(props.rootGroup)), onHide: () => setShowClassSelectionWarning(false), onChange: (selectedIds) => { onSelectedClassesChanged(selectedIds); } }), (0, jsx_runtime_1.jsx)("div", { className: "presentation-property-filter-builder", children: (0, jsx_runtime_1.jsx)(components_react_1.PropertyFilterBuilderRenderer, { ...restProps, ruleValueRenderer: (rendererProps) => ((0, jsx_runtime_1.jsx)(PresentationFilterBuilder_js_1.PresentationFilterBuilderValueRenderer, { ...rendererProps, descriptorInputKeys: descriptorInputKeys, imodel: imodel, descriptor: descriptor, selectedClasses: selectedClasses })) }) })] })] })); } function createOption(classInfo) { return { label: classInfo.label, value: classInfo.id }; } /** * Custom hook that extracts properties and classes from [Descriptor]($presentation-common) and creates props that can be used by [[InstanceFilterBuilder]] component. * * This hook also makes sure that when classes are selected available properties list is updated to contain only properties found on selected classes and vice versa - * when property is selected in one of the rules selected classes list is updated to contain only classes that has access to that property. * @internal */ function usePresentationInstanceFilteringProps(descriptor, imodel, initialActiveClasses) { const { propertyInfos, propertyRenderer } = (0, PresentationFilterBuilder_js_1.useInstanceFilterPropertyInfos)({ descriptor }); const classes = usePropertyClasses({ descriptor }); const { activeClasses, changeActiveClasses, isFilteringClasses, filterClassesByProperty } = useActiveClasses({ imodel, availableClasses: classes, initialActiveClasses, }); const { properties, isFilteringProperties } = usePropertiesFilteringByClass({ imodel, availableProperties: propertyInfos, activeClasses }); const onRulePropertySelected = (0, react_1.useCallback)((property) => { const propertyInfo = propertyInfos.find((info) => info.propertyDescription.name === property.name); if (propertyInfo) { filterClassesByProperty(propertyInfo); } }, [propertyInfos, filterClassesByProperty]); return { onRulePropertySelected, onSelectedClassesChanged: (0, react_1.useCallback)((classIds) => changeActiveClasses(classIds), [changeActiveClasses]), propertyRenderer, properties, classes, selectedClasses: activeClasses, isDisabled: isFilteringClasses || isFilteringProperties, }; } function usePropertyClasses({ descriptor }) { return (0, react_1.useMemo)(() => { const uniqueClasses = new Map(); descriptor.selectClasses.forEach((selectClass) => uniqueClasses.set(selectClass.selectClassInfo.id, selectClass.selectClassInfo)); return [...uniqueClasses.values()]; }, [descriptor]); } function usePropertiesFilteringByClass({ imodel, availableProperties, activeClasses }) { const [filteredProperties, setFilteredProperties] = (0, react_1.useState)(); const [isFilteringProperties, setIsFilteringProperties] = (0, react_1.useState)(false); const properties = (0, react_1.useMemo)(() => (filteredProperties ?? availableProperties).map((info) => info.propertyDescription), [availableProperties, filteredProperties]); const classChanges = (0, react_1.useRef)(new rxjs_1.BehaviorSubject([])); (0, react_1.useEffect)(() => { classChanges.current.next(activeClasses); }, [activeClasses]); // filter properties by selected classes (0, react_1.useEffect)(() => { const subscription = classChanges.current .pipe((0, map_1.map)((classes) => { if (classes.length === 0) { return (0, rxjs_1.of)(undefined); } setIsFilteringProperties(true); return (0, rxjs_1.from)(computePropertiesByClasses(availableProperties, classes, imodel)); }), (0, switchAll_1.switchAll)()) .subscribe({ next: (infos) => { setFilteredProperties(infos); setIsFilteringProperties(false); }, }); return () => { subscription.unsubscribe(); }; }, [imodel, availableProperties]); return { properties, isFilteringProperties, }; } function useActiveClasses({ imodel, availableClasses, initialActiveClasses }) { const [activeClasses, setActiveClasses] = (0, react_1.useState)(initialActiveClasses ?? []); const [isFilteringClasses, setIsFilteringClasses] = (0, react_1.useState)(false); const availableClassesRef = (0, react_1.useRef)(availableClasses); (0, react_1.useEffect)(() => { if (availableClassesRef.current !== availableClasses) { setActiveClasses([]); availableClassesRef.current = availableClasses; } }, [availableClasses]); const filterClassesByProperty = (0, react_1.useCallback)((property) => { setIsFilteringClasses(true); void (async () => { const newActiveClasses = await computeClassesByProperty(activeClasses.length === 0 ? availableClasses : activeClasses, property, imodel); setActiveClasses(newActiveClasses); setIsFilteringClasses(false); })(); }, [activeClasses, availableClasses, imodel]); const changeActiveClasses = (0, react_1.useCallback)((classIds) => { const newSelectedClasses = availableClasses.filter((availableClass) => classIds.findIndex((classId) => classId === availableClass.id) !== -1); setActiveClasses(newSelectedClasses); }, [availableClasses]); return { activeClasses, isFilteringClasses, changeActiveClasses, filterClassesByProperty, }; } async function computePropertiesByClasses(properties, classes, imodel) { const metadataProvider = (0, ECMetadataProvider_js_1.getIModelMetadataProvider)(imodel); const ecClassInfos = await Promise.all(classes.map(async (info) => metadataProvider.getECClassInfo(info.id))); const filteredProperties = []; for (const prop of properties) { // property should be shown if at least one of selected classes is derived from property source class if (ecClassInfos.some((info) => info && prop.sourceClassIds.some((sourceClassId) => info.isDerivedFrom(sourceClassId)))) { filteredProperties.push(prop); } } return filteredProperties.length === properties.length ? undefined : filteredProperties; } async function computeClassesByProperty(classes, property, imodel) { const metadataProvider = (0, ECMetadataProvider_js_1.getIModelMetadataProvider)(imodel); const propertyClasses = (await Promise.all(property.sourceClassIds.map(async (sourceClassId) => { return metadataProvider.getECClassInfo(sourceClassId); }))).filter((propertyClass) => propertyClass !== undefined); /* c8 ignore next 3 */ if (propertyClasses.length === 0) { return classes; } const classesWithProperty = []; for (const currentClass of classes) { if (propertyClasses.some((propertyClass) => propertyClass.isBaseOf(currentClass.id))) { classesWithProperty.push(currentClass); } } return classesWithProperty; } //# sourceMappingURL=InstanceFilterBuilder.js.map