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