@itwin/presentation-components
Version:
React components based on iTwin.js Presentation library
175 lines • 10.3 kB
JavaScript
;
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