UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

170 lines 9.14 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "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 */ import "./PresentationInstanceFilterDialog.scss"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { usePropertyFilterBuilder } from "@itwin/components-react"; import { SvgError } from "@itwin/itwinui-illustrations-react"; import { Button, Dialog, NonIdealState, ProgressRadial } from "@itwin/itwinui-react"; import { translate, useDelay } from "../common/Utils.js"; import { InstanceFilterBuilder, usePresentationInstanceFilteringProps } from "./InstanceFilterBuilder.js"; import { PresentationInstanceFilter } from "./PresentationInstanceFilter.js"; import { filterRuleValidator, isFilterNonEmpty } from "./Utils.js"; /** * Dialog component that renders [[InstanceFilterBuilder]] inside. * @public */ export function PresentationInstanceFilterDialog(props) { const { isOpen, title, ...restProps } = props; return (_jsxs(Dialog, { className: "presentation-instance-filter-dialog", isOpen: isOpen, onClose: props.onClose, closeOnEsc: false, preventDocumentScroll: true, trapFocus: true, isDraggable: true, isResizable: true, portal: true, children: [_jsx(Dialog.Backdrop, {}), _jsxs(Dialog.Main, { className: "presentation-instance-filter-dialog-content-container", children: [_jsx(Dialog.TitleBar, { className: "presentation-instance-filter-title", titleText: title ? title : translate("instance-filter-builder.filter") }), _jsx(ErrorBoundary, { fallback: _jsx(ErrorState, {}), children: _jsx(FilterDialogContent, { ...restProps }) })] })] })); } function FilterDialogContent({ propertiesSource, ...restProps }) { const { propertiesSource: loadedPropertiesSource, isLoading } = useDelayLoadedPropertiesSource(propertiesSource); if (isLoading) { return _jsx(DelayedCenteredProgressRadial, {}); } if (!loadedPropertiesSource) { return null; } return _jsx(LoadedFilterDialogContent, { ...restProps, descriptor: loadedPropertiesSource.descriptor, descriptorInputKeys: loadedPropertiesSource.inputKeys }); } function useDelayLoadedPropertiesSource(sourceOrGetter) { const [{ source, isLoading }, setState] = useState(() => typeof sourceOrGetter === "function" ? { source: undefined, isLoading: false, } : { source: sourceOrGetter, isLoading: false, }); useEffect(() => { let disposed = false; if (typeof sourceOrGetter !== "function") { setState({ source: sourceOrGetter, isLoading: false }); return; } const updateState = (...params) => { if (!disposed) { setState(...params); } }; updateState({ source: undefined, isLoading: true }); void (async () => { try { const newDescriptor = await sourceOrGetter(); updateState({ source: newDescriptor, isLoading: false, }); } catch (error) { updateState(() => { // throw error in setSate callback for it to be caught by ErrorBoundary throw error; }); } })(); return () => { disposed = true; }; }, [sourceOrGetter]); return { propertiesSource: source, isLoading }; } function LoadedFilterDialogContent(props) { const { initialFilter, descriptor, imodel, filterResultsCountRenderer, descriptorInputKeys, onApply, onReset, onClose, toolbarButtonsRenderer } = props; const initialFilterInfo = useInitialFilter(descriptor, initialFilter); const [initialPropertyFilter] = useState(() => { if (!initialFilterInfo?.filter) { return undefined; } return PresentationInstanceFilter.toComponentsPropertyFilter(descriptor, initialFilterInfo.filter); }); const { rootGroup, actions, buildFilter } = usePropertyFilterBuilder({ initialFilter: initialPropertyFilter, ruleValidator: filterRuleValidator, }); const filteringProps = usePresentationInstanceFilteringProps(descriptor, imodel, initialFilterInfo?.usedClasses); const getFilterInfo = useCallback((options) => { const filter = buildFilter(options); if (!filter && filteringProps.selectedClasses.length === 0) { return undefined; } return { filter: filter ? PresentationInstanceFilter.fromComponentsPropertyFilter(descriptor, filter) : undefined, usedClasses: filteringProps.selectedClasses, }; }, [buildFilter, descriptor, filteringProps.selectedClasses]); const handleReset = () => { filteringProps.selectedClasses = []; filteringProps.onSelectedClassesChanged([]); actions.removeAllItems(); onReset && onReset(); }; const onSelectedClassesChanged = (classIds) => { filteringProps.onSelectedClassesChanged(classIds); actions.removeAllItems(); }; const throwError = useThrowError(); const handleApply = () => { try { const result = getFilterInfo(); // do not invoke apply if filter was not built and it is non empty. if (result?.filter === undefined && isFilterNonEmpty(rootGroup)) { return; } onApply(result); } catch (error) { throwError(error); } }; const handleClose = () => { onClose && onClose(); }; return (_jsxs(_Fragment, { children: [_jsx(Dialog.Content, { className: "presentation-instance-filter-content", children: _jsx(InstanceFilterBuilder, { ...filteringProps, onSelectedClassesChanged: onSelectedClassesChanged, rootGroup: rootGroup, actions: actions, imodel: imodel, descriptor: descriptor, descriptorInputKeys: descriptorInputKeys }) }), _jsxs("div", { className: "presentation-instance-filter-dialog-bottom-container", children: [_jsx("div", { children: filterResultsCountRenderer ? _jsx(ResultsRenderer, { buildFilter: getFilterInfo, renderer: filterResultsCountRenderer }) : null }), _jsx(Dialog.ButtonBar, { className: "presentation-instance-filter-button-bar", children: toolbarButtonsRenderer ? (toolbarButtonsRenderer({ handleApply, handleReset, handleClose })) : (_jsx(ToolbarButtonsRenderer, { handleApply: handleApply, handleReset: handleReset, handleClose: handleClose })) })] })] })); } function useInitialFilter(descriptor, initialFilter) { const initializedFilter = useRef(); if (initializedFilter.current === undefined) { initializedFilter.current = { filterInfo: typeof initialFilter === "function" ? initialFilter(descriptor) : initialFilter }; } return initializedFilter.current.filterInfo; } function ToolbarButtonsRenderer({ handleApply, handleClose, handleReset }) { return (_jsxs(_Fragment, { children: [_jsx(Button, { className: "presentation-instance-filter-dialog-apply-button", styleType: "high-visibility", onClick: handleApply, children: translate("instance-filter-builder.apply") }), _jsx(Button, { className: "presentation-instance-filter-dialog-close-button", onClick: handleClose, children: translate("instance-filter-builder.cancel") }), _jsx(Button, { className: "presentation-instance-filter-dialog-reset-button", onClick: handleReset, children: translate("instance-filter-builder.reset") })] })); } function ResultsRenderer({ buildFilter, renderer }) { const filter = useMemo(() => buildFilter({ ignoreErrors: true }), [buildFilter]); if (!filter) { return null; } return _jsx(_Fragment, { children: renderer(filter) }); } function DelayedCenteredProgressRadial() { const show = useDelay(); if (!show) { return null; } return (_jsx("div", { className: "presentation-instance-filter-dialog-progress", children: _jsx(ProgressRadial, { indeterminate: true, size: "large" }) })); } function ErrorState() { return (_jsx("div", { style: { width: "100%", height: "100%", position: "relative", overflow: "hidden" }, children: _jsx(NonIdealState, { svg: _jsx(SvgError, {}), heading: translate("general.error"), description: translate("general.generic-error-description") }) })); } // ErrorBoundary only catches errors that are thrown in React lifecycle methods. For event handlers and // async function errors can be rethrown from `setState` callback. function useThrowError() { const [_, setSate] = useState({}); return (error) => { setSate(() => { throw error; }); }; } //# sourceMappingURL=PresentationInstanceFilterDialog.js.map