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