UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

199 lines 10 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. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Table */ Object.defineProperty(exports, "__esModule", { value: true }); exports.usePresentationTable = usePresentationTable; exports.usePresentationTableWithUnifiedSelection = usePresentationTableWithUnifiedSelection; const react_1 = require("react"); const rxjs_1 = require("rxjs"); const core_bentley_1 = require("@itwin/core-bentley"); const presentation_common_1 = require("@itwin/presentation-common"); const presentation_core_interop_1 = require("@itwin/presentation-core-interop"); const presentation_frontend_1 = require("@itwin/presentation-frontend"); const presentation_shared_1 = require("@itwin/presentation-shared"); const unified_selection_1 = require("@itwin/unified-selection"); const UseColumns_js_1 = require("./UseColumns.js"); const UseRows_js_1 = require("./UseRows.js"); const UseTableOptions_js_1 = require("./UseTableOptions.js"); /** * Custom hook that loads data for generic table component. * @throws on failure to get table data. The error is thrown in the React's render loop, so it can be caught using an error boundary. * @public */ function usePresentationTable(props) { const { imodel, ruleset, keys, pageSize, columnMapper, rowMapper } = props; const columns = (0, UseColumns_js_1.useColumns)({ imodel, ruleset, keys }); const { options, sort, filter } = (0, UseTableOptions_js_1.useTableOptions)({ columns }); const { rows, isLoading, loadMoreRows } = (0, UseRows_js_1.useRows)({ imodel, ruleset, keys, pageSize, options }); return { columns: (0, react_1.useMemo)(() => columns?.map(columnMapper), [columns, columnMapper]), rows: (0, react_1.useMemo)(() => rows?.map(rowMapper), [rows, rowMapper]), isLoading, loadMoreRows, sort, filter, }; } /** * Custom hook that load data for generic table component. It uses [Unified Selection]($docs/presentation/unified-selection/index.md) to get keys defining what to load rows for. * * @throws on failure to get table data. The error is thrown in the React's render loop, so it can be caught using an error boundary. * @public */ function usePresentationTableWithUnifiedSelection(props) { const { imodel, ruleset, pageSize, columnMapper, rowMapper, selectionStorage } = props; const [tableName] = (0, react_1.useState)(() => `UnifiedSelectionTable_${core_bentley_1.Guid.createValue()}`); const [selectedRows, setSelectedRows] = (0, react_1.useState)(); const { keys: { keys, isLoading: isLoadingKeys }, getSelection, replaceSelection, selectionChange, } = useSelectionHandler({ imodel, selectionStorage, tableName }); const columns = (0, UseColumns_js_1.useColumns)({ imodel, ruleset, keys }); const { options, sort, filter } = (0, UseTableOptions_js_1.useTableOptions)({ columns }); const { rows, isLoading: isLoadingRows, loadMoreRows } = (0, UseRows_js_1.useRows)({ imodel, ruleset, keys, pageSize, options }); (0, react_1.useEffect)(() => { const updateSelectedRows = new rxjs_1.Subject(); const subscription = updateSelectedRows .pipe((0, rxjs_1.mergeMap)((level) => { if (level > 1) { // ignore all selection changes with level > 1 return rxjs_1.EMPTY; } if (level === 0) { // selection at level 0 defines what the table shows, so just clear the selection return (0, rxjs_1.of)([]); } return (0, rxjs_1.from)(getSelection({ level: 1 })).pipe((0, rxjs_1.debounceTime)(0), (0, rxjs_1.map)((selectedKeys) => { const rowsToAddToSelection = []; selectedKeys.forEach((selectable) => { const selectedRow = rows.find((row) => { // table content is built using the legacy Presentation library, where full class name format is // "schema:class". In the unified selection library, the format is "schema.class". const { schemaName, className } = (0, presentation_shared_1.parseFullClassName)(selectable.className); return row.key === JSON.stringify({ className: `${schemaName}:${className}`, id: selectable.id }); }); if (selectedRow !== undefined) { rowsToAddToSelection.push(selectedRow); } }); return rowsToAddToSelection; })); })) .subscribe({ next: (rowsToSelect) => { setSelectedRows(rowsToSelect); }, }); updateSelectedRows.next(1); const removeListener = selectionChange.addListener((level) => updateSelectedRows.next(level)); return () => { subscription.unsubscribe(); removeListener(); }; }, [rows, imodel, selectionChange, getSelection]); const onSelect = (selectedRowKeys) => { const selectables = []; for (const selectedRowKey of selectedRowKeys) { if (!rows.find((row) => row.key === selectedRowKey)) { continue; } const selectableKey = JSON.parse(selectedRowKey); selectables.push(selectableKey); } replaceSelection({ source: tableName, selectables, level: 1 }); }; return { columns: (0, react_1.useMemo)(() => columns?.map(columnMapper), [columns, columnMapper]), rows: (0, react_1.useMemo)(() => rows.map(rowMapper), [rows, rowMapper]), isLoading: !columns || isLoadingRows || isLoadingKeys, loadMoreRows, sort, filter, onSelect, selectedRows: (0, react_1.useMemo)(() => (selectedRows ?? []).map(rowMapper), [selectedRows, rowMapper]), }; } function useSelectionHandler({ imodel, selectionStorage, tableName }) { const [selectionChange] = (0, react_1.useState)(() => new core_bentley_1.BeEvent()); (0, react_1.useEffect)(() => { if (selectionStorage) { return selectionStorage.selectionChangeEvent.addListener((args) => { if (args.imodelKey === (0, presentation_core_interop_1.createIModelKey)(imodel) && args.source !== tableName) { selectionChange.raiseEvent(args.level); } }); } // eslint-disable-next-line @typescript-eslint/no-deprecated return presentation_frontend_1.Presentation.selection.selectionChange.addListener((args) => { if (imodel === args.imodel && args.source !== tableName) { selectionChange.raiseEvent(args.level); } }); }, [imodel, selectionStorage, tableName, selectionChange]); const getSelection = (0, react_1.useCallback)(async (args) => { return selectionStorage ? loadInstanceKeysFromSelectables(selectionStorage.getSelection({ imodelKey: (0, presentation_core_interop_1.createIModelKey)(imodel), level: args.level })) : // eslint-disable-next-line @typescript-eslint/no-deprecated loadInstanceKeysFromKeySet(presentation_frontend_1.Presentation.selection.getSelection(imodel, args.level)); }, [imodel, selectionStorage]); const replaceSelection = (0, react_1.useCallback)((args) => { return selectionStorage ? selectionStorage.replaceSelection({ imodelKey: (0, presentation_core_interop_1.createIModelKey)(imodel), source: args.source, level: args.level, selectables: args.selectables, }) : // eslint-disable-next-line @typescript-eslint/no-deprecated presentation_frontend_1.Presentation.selection.replaceSelection(args.source, imodel, args.selectables, args.level); }, [imodel, selectionStorage]); const keys = useUnifiedSelectionKeys({ getSelection, selectionChange }); return { keys, getSelection, replaceSelection, selectionChange, }; } async function loadInstanceKeysFromSelectables(selectables) { const keys = []; for await (const selectable of unified_selection_1.Selectables.load(selectables)) { keys.push(selectable); } return keys; } async function loadInstanceKeysFromKeySet(keySet) { const keys = []; keySet.forEach((key) => { if (presentation_common_1.Key.isInstanceKey(key)) { keys.push(key); } else if (presentation_common_1.NodeKey.isInstancesNodeKey(key)) { keys.push(...key.instanceKeys); } }); return keys; } function useUnifiedSelectionKeys({ getSelection, selectionChange, }) { const [state, setState] = (0, react_1.useState)(() => ({ isLoading: false, keys: new presentation_common_1.KeySet() })); (0, react_1.useEffect)(() => { const update = new rxjs_1.Subject(); const subscription = update .pipe((0, rxjs_1.tap)(() => setState((prev) => ({ ...prev, isLoading: true }))), (0, rxjs_1.switchMap)(async () => getSelection({ level: 0 })), (0, rxjs_1.map)((selectables) => new presentation_common_1.KeySet(selectables))) .subscribe({ next: (newKeys) => { setState({ isLoading: false, keys: newKeys }); }, }); update.next(); const removeListener = selectionChange.addListener(() => update.next()); return () => { subscription.unsubscribe(); removeListener(); }; }, [getSelection, selectionChange]); return state; } //# sourceMappingURL=UsePresentationTable.js.map