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