@itwin/presentation-components
Version:
React components based on iTwin.js Presentation library
171 lines • 6.61 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 "../../common/DisposePolyfill.js";
import { useEffect, useMemo, useState } from "react";
import { EMPTY, from, map, mergeMap, toArray } from "rxjs";
import { ContentFlags, ContentSpecificationTypes, KeySet, RuleTypes, } from "@itwin/presentation-common";
import { Presentation } from "@itwin/presentation-frontend";
import { FILTER_WARNING_OPTION, VALUE_BATCH_SIZE } from "./ItemsLoader.js";
/** @internal */
export function useNavigationPropertyTargetsLoader(props) {
const { imodel, ruleset, filterText, initialSelectedTarget } = props;
const [itemsLoader, setItemsLoader] = useState();
const [state, setLoadedOptions] = useState({
options: [],
isLoading: false,
});
// Get initial loader and values
useEffect(() => {
setLoadedOptions({ options: [], isLoading: false });
if (!ruleset) {
return;
}
const loader = new NavigationPropertyItemsLoader(() => {
setLoadedOptions((prev) => ({ ...prev, isLoading: true }));
}, (newItems) => {
setLoadedOptions((prev) => ({
options: [...prev.options, ...newItems],
isLoading: false,
}));
}, async (filter) => getItems(imodel, ruleset, filter));
void loader.loadItems(initialSelectedTarget ?? "");
setItemsLoader(loader);
return () => {
loader[Symbol.dispose]();
};
}, [imodel, initialSelectedTarget, ruleset]);
// On filter text change, check if more items need to be loaded
useEffect(() => {
if (!itemsLoader) {
return;
}
const timeout = setTimeout(() => {
void itemsLoader.loadItems(filterText);
}, 250);
return () => {
clearTimeout(timeout);
};
}, [itemsLoader, filterText]);
return {
selectOptions: useMemo(() => {
const options = state.options.map((option) => {
return {
label: option.label.displayValue,
value: option.label.displayValue,
};
});
if (options.length >= VALUE_BATCH_SIZE) {
options.push(FILTER_WARNING_OPTION);
}
return options;
}, [state.options]),
loadedOptions: state.options,
isLoading: state.isLoading,
};
}
/** @internal */
export function useNavigationPropertyTargetsRuleset(getNavigationPropertyInfo, property) {
const [ruleset, setRuleset] = useState();
useEffect(() => {
let disposed = false;
void (async () => {
const propertyInfo = await getNavigationPropertyInfo(property);
if (!disposed && propertyInfo) {
setRuleset(createNavigationPropertyTargetsRuleset(propertyInfo));
}
})();
return () => {
disposed = true;
};
}, [property, getNavigationPropertyInfo]);
return ruleset;
}
function createNavigationPropertyTargetsRuleset(propertyInfo) {
const [schemaName, className] = propertyInfo.targetClassInfo.name.split(":");
return {
id: `navigation-property-targets`,
rules: [
{
ruleType: RuleTypes.Content,
specifications: [
{
specType: ContentSpecificationTypes.ContentInstancesOfSpecificClasses,
classes: { schemaName, classNames: [className], arePolymorphic: propertyInfo.isTargetPolymorphic },
},
],
},
],
};
}
async function getItems(imodel, ruleset, filter) {
const requestProps = {
imodel,
rulesetOrId: ruleset,
keys: new KeySet(),
descriptor: {
contentFlags: ContentFlags.ShowLabels | ContentFlags.NoFields,
fieldsFilterExpression: filter ? `/DisplayLabel/ ~ \"%${filter}%\"` : undefined,
},
paging: { size: VALUE_BATCH_SIZE },
};
const items = await new Promise((resolve, reject) => {
(Presentation.presentation.getContentIterator
? from(Presentation.presentation.getContentIterator(requestProps)).pipe(mergeMap((result) => (result ? result.items : EMPTY)), toArray())
: // eslint-disable-next-line @typescript-eslint/no-deprecated
from(Presentation.presentation.getContent(requestProps)).pipe(map((content) => (content ? content.contentSet : [])))).subscribe({
next: resolve,
error: reject,
});
});
const results = items.map((item) => ({
label: item.label,
key: item.primaryKeys[0],
}));
return results;
}
/** @internal */
export class NavigationPropertyItemsLoader {
_beforeLoad;
_onItemsLoaded;
_loadItems;
_loadedItems = [];
_isLoading = false;
_disposed = false;
constructor(_beforeLoad, _onItemsLoaded, _loadItems) {
this._beforeLoad = _beforeLoad;
this._onItemsLoaded = _onItemsLoaded;
this._loadItems = _loadItems;
}
[Symbol.dispose]() {
this._disposed = true;
}
async loadItems(filterText) {
if (this._isLoading || filterText === undefined || (filterText === "" && this._loadedItems.length >= VALUE_BATCH_SIZE)) {
return;
}
const filteredItems = this._loadedItems.filter((option) => option.label.displayValue.toLowerCase().includes(filterText.toLowerCase()));
if (filteredItems.length >= VALUE_BATCH_SIZE) {
return;
}
this._isLoading = true;
this._beforeLoad();
const options = await this._loadItems(filterText);
if (this._disposed) {
return;
}
const uniqueOptions = options.filter((option) => {
return !this._loadedItems.some((loadedItem) => {
return loadedItem.key.id === option.key.id && loadedItem.label.displayValue === option.label.displayValue;
});
});
this._loadedItems.push(...uniqueOptions);
this._onItemsLoaded(uniqueOptions);
this._isLoading = false;
}
}
//# sourceMappingURL=UseNavigationPropertyTargetsLoader.js.map