UNPKG

@itwin/presentation-components

Version:

React components based on iTwin.js Presentation library

174 lines 7.43 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Internal */ import { useEffect, useRef, useState } from "react"; import { EMPTY, from, map, mergeMap, of, Subject, toArray } from "rxjs"; import { assert } from "@itwin/core-bentley"; import { IModelApp } from "@itwin/core-frontend"; import { Content, DefaultContentDisplayTypes, KeySet, traverseContent, } from "@itwin/presentation-common"; import { createIModelKey } from "@itwin/presentation-core-interop"; import { Presentation } from "@itwin/presentation-frontend"; import { InternalPropertyRecordsBuilder } from "../common/ContentBuilder.js"; import { useErrorState } from "../common/Utils.js"; /** @internal */ export function useRows(props) { const { imodel, ruleset, keys, pageSize, options } = props; const setErrorState = useErrorState(); const [state, setState] = useState({ isLoading: false, rows: [], total: 0 }); const loaderRef = useRef(noopRowsLoader); useEffect(() => { setState((prev) => ({ ...prev, rows: [], total: 0 })); const { observable, ...loader } = createRowsLoader({ imodel, ruleset, keys, pageSize, options, onPageLoadStart: () => setState((prev) => ({ ...prev, isLoading: true })), }); loaderRef.current = loader; const subscription = observable.subscribe({ next: ({ total, rowDefinitions, offset }) => { setState((prev) => { const newRows = [...prev.rows]; newRows.splice(offset, rowDefinitions.length, ...rowDefinitions); return { ...prev, isLoading: false, rows: newRows, total }; }); }, error: (err) => { setErrorState(err); }, }); loaderRef.current.loadPage(0); return () => { subscription.unsubscribe(); loaderRef.current = noopRowsLoader; }; }, [imodel, ruleset, keys, pageSize, options, setErrorState]); useEffect(() => { return IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener(() => { loaderRef.current.reload(state.rows.length); }); }, [state.rows]); return { rows: state.rows, isLoading: state.isLoading, loadMoreRows: () => { if (state.rows.length === state.total) { return; } loaderRef.current.loadPage(state.rows.length); }, }; } function createRowsLoader({ imodel, ruleset, keys, pageSize, options, onPageLoadStart, }) { const loaderSubject = new Subject(); const loaderObservable = loaderSubject.pipe(mergeMap((loaderOptions) => { if (keys.isEmpty) { return EMPTY; } switch (loaderOptions.action) { case "load-page": { onPageLoadStart(); return from(loadRows(imodel, ruleset, keys, { start: loaderOptions.pageStart, size: pageSize }, options)); } case "reload": { return loaderOptions.loadedRowsCount === 0 ? EMPTY : createReloadObs(imodel, ruleset, keys, options, loaderOptions.loadedRowsCount); } } })); return { observable: loaderObservable, loadPage: (pageStart) => { loaderSubject.next({ action: "load-page", pageStart }); }, reload: (rowsCount) => { loaderSubject.next({ action: "reload", loadedRowsCount: rowsCount }); }, }; } /** @internal */ export const ROWS_RELOAD_PAGE_SIZE = 1000; function createReloadObs(imodel, ruleset, keys, options, loadedItemsCount) { const lastPageIndex = Math.floor(loadedItemsCount / ROWS_RELOAD_PAGE_SIZE); const lastPageSize = loadedItemsCount % ROWS_RELOAD_PAGE_SIZE; const pages = []; for (let i = 0; i <= lastPageIndex; i++) { pages.push({ start: i * ROWS_RELOAD_PAGE_SIZE, size: i === lastPageIndex ? lastPageSize : ROWS_RELOAD_PAGE_SIZE }); } return from(pages).pipe(mergeMap((pageOptions) => from(loadRows(imodel, ruleset, keys, pageOptions, options)))); } async function loadRows(imodel, ruleset, keys, paging, options) { const requestProps = { imodel, keys: new KeySet(keys), descriptor: { displayType: DefaultContentDisplayTypes.Grid, sorting: options.sorting, fieldsFilterExpression: options.fieldsFilterExpression, }, rulesetOrId: ruleset, paging, }; return new Promise((resolve, reject) => { (Presentation.presentation.getContentIterator ? from(Presentation.presentation.getContentIterator(requestProps)).pipe(mergeMap((result) => { if (!result) { return of(undefined); } return from(result.items).pipe(toArray(), map((items) => ({ total: result.total, content: new Content(result.descriptor, items) }))); })) : // eslint-disable-next-line @typescript-eslint/no-deprecated from(Presentation.presentation.getContentAndSize(requestProps)).pipe(map((result) => (result ? { total: result.size, content: result.content } : undefined)))) .pipe(map((result) => result ? { rowDefinitions: createRows(result.content, imodel), total: result.total, offset: paging.start } : { rowDefinitions: [], total: 0, offset: 0 })) .subscribe({ next: resolve, error: reject }); }); } function createRows(content, imodel) { const rowsBuilder = new RowsBuilder({ imodel }); // note: using deprecated `traverseContent`, because we can't use the replacement `createContentTraverser` due to our peer dep version // eslint-disable-next-line @typescript-eslint/no-deprecated traverseContent(rowsBuilder, content); return rowsBuilder.rows; } class RowsBuilder extends InternalPropertyRecordsBuilder { rows = []; _currentRow = undefined; constructor({ imodel }) { super((item) => ({ item, append: (record) => { if (record.fieldHierarchy.field.isNestedContentField()) { return; } assert(this._currentRow !== undefined); this._currentRow.cells.push({ key: record.fieldHierarchy.field.name, record: record.record }); }, }), (record) => { record.imodelKey = createIModelKey(imodel); }); } startItem(props) { const key = JSON.stringify(props.item.primaryKeys[0]); this._currentRow = { key, cells: [] }; return super.startItem(props); } finishItem() { assert(this._currentRow !== undefined); this.rows.push(this._currentRow); this._currentRow = undefined; super.finishItem(); } } /* v8 ignore start -- @preserve */ const noopRowsLoader = { loadPage: () => { }, reload: () => { } }; /* v8 ignore stop -- @preserve */ //# sourceMappingURL=UseRows.js.map