diff --git a/common/changes/@itwin/imodel-browser-react/itwin-grid-doc_2024-12-11-20-54.json b/common/changes/@itwin/imodel-browser-react/itwin-grid-doc_2024-12-11-20-54.json deleted file mode 100644 index e3bfd07b..00000000 --- a/common/changes/@itwin/imodel-browser-react/itwin-grid-doc_2024-12-11-20-54.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@itwin/imodel-browser-react", - "comment": "Update access token docs for iTwinGrid and iModelGrid", - "type": "none" - } - ], - "packageName": "@itwin/imodel-browser-react" -} \ No newline at end of file diff --git a/packages/apps/storybook/package.json b/packages/apps/storybook/package.json index 1a9d2c58..44ed51ea 100644 --- a/packages/apps/storybook/package.json +++ b/packages/apps/storybook/package.json @@ -12,7 +12,7 @@ "@itwin/create-imodel-react": "^2.0.0", "@itwin/delete-imodel-react": "^2.0.0", "@itwin/delete-itwin-react": "^2.0.0", - "@itwin/imodel-browser-react": "~2.1.2", + "@itwin/imodel-browser-react": "~2.2.0", "@itwin/itwinui-react": "^2.12.18", "@itwin/manage-versions-react": "~2.0.0", "@itwin/storybook-auth-addon": "^0.1.0", diff --git a/packages/apps/storybook/src/imodel-browser/IModelGrid.stories.tsx b/packages/apps/storybook/src/imodel-browser/IModelGrid.stories.tsx index 8dd681d1..01e665e1 100644 --- a/packages/apps/storybook/src/imodel-browser/IModelGrid.stories.tsx +++ b/packages/apps/storybook/src/imodel-browser/IModelGrid.stories.tsx @@ -216,7 +216,7 @@ const useIndividualState = (iModel: IModelFull, props: IModelTileProps) => { }), [fetchVersionsList, selection?.displayName, versions] ); - // Override the thumbnailClick so it recieves the selected version too. + // Override the thumbnailClick so it receives the selected version too. // Not great typewise, but it is an example of what someone could do if it was really needed. const onThumbnailClick = React.useCallback( (iModel: IModelFull) => { @@ -241,7 +241,7 @@ export const WithPostProcessCallback: Story = withAccessTokenOverride((args) => { const [filter, setFilter] = React.useState(""); const filterOrAddStartTile = React.useCallback( - (iModels: IModelFull[], status: DataStatus) => { + (iModels: IModelFull[], status?: DataStatus) => { if (status !== DataStatus.Complete) { return iModels; } diff --git a/packages/modules/imodel-browser/CHANGELOG.json b/packages/modules/imodel-browser/CHANGELOG.json index 79c06bf3..bcb90cf0 100644 --- a/packages/modules/imodel-browser/CHANGELOG.json +++ b/packages/modules/imodel-browser/CHANGELOG.json @@ -1,6 +1,23 @@ { "name": "@itwin/imodel-browser-react", "entries": [ + { + "version": "2.2.0", + "tag": "@itwin/imodel-browser-react_v2.2.0", + "date": "Tue, 07 Jan 2025 19:47:55 GMT", + "comments": { + "none": [ + { + "comment": "Update access token docs for iTwinGrid and iModelGrid" + } + ], + "minor": [ + { + "comment": "provide option to refresh the grid after an itwin/imodel action" + } + ] + } + }, { "version": "2.1.2", "tag": "@itwin/imodel-browser-react_v2.1.2", diff --git a/packages/modules/imodel-browser/CHANGELOG.md b/packages/modules/imodel-browser/CHANGELOG.md index a8deead4..41f513f6 100644 --- a/packages/modules/imodel-browser/CHANGELOG.md +++ b/packages/modules/imodel-browser/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @itwin/imodel-browser-react -This log was last generated on Mon, 09 Dec 2024 19:40:38 GMT and should not be manually modified. +This log was last generated on Tue, 07 Jan 2025 19:47:55 GMT and should not be manually modified. + +## 2.2.0 +Tue, 07 Jan 2025 19:47:55 GMT + +### Minor changes + +- provide option to refresh the grid after an itwin/imodel action ## 2.1.2 Mon, 09 Dec 2024 19:40:38 GMT diff --git a/packages/modules/imodel-browser/package.json b/packages/modules/imodel-browser/package.json index 844664b4..8b9fa675 100644 --- a/packages/modules/imodel-browser/package.json +++ b/packages/modules/imodel-browser/package.json @@ -2,7 +2,7 @@ "name": "@itwin/imodel-browser-react", "description": "Components that let the user browse the iModels of a context and select one.", "repository": "https://github.com/iTwin/admin-components-react/tree/main/packages/modules/imodel-browser", - "version": "2.1.2", + "version": "2.2.0", "main": "cjs/index.js", "module": "esm/index.js", "types": "cjs/index.d.ts", diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx index f6ae2a05..d7c44cc3 100644 --- a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx +++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx @@ -24,6 +24,7 @@ describe("ITwinGrid", () => { ], status: DataStatus.Complete, fetchMore: undefined, + refetchITwins: jest.fn(), }); }); @@ -37,6 +38,7 @@ describe("ITwinGrid", () => { iTwins: [], status: DataStatus.Complete, fetchMore: undefined, + refetchITwins: jest.fn(), }); // Act @@ -58,11 +60,11 @@ describe("ITwinGrid", () => { it("should not refetch iTwins favorites when component rerenders", async () => { // Arrange - const fetchMore = jest.fn(); jest.spyOn(useITwinData, "useITwinData").mockReturnValue({ iTwins: [], status: DataStatus.Complete, - fetchMore, + fetchMore: jest.fn(), + refetchITwins: jest.fn(), }); // Act const signal = new AbortController().signal; diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx index 5aa93c3a..1f562c6d 100644 --- a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx +++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx @@ -145,6 +145,7 @@ export const ITwinGrid = ({ iTwins: fetchedItwins, status: fetchStatus, fetchMore, + refetchITwins, } = useITwinData({ requestType, iTwinSubClass, @@ -168,6 +169,7 @@ export const ITwinGrid = ({ iTwinFavorites, addITwinToFavorites, removeITwinFromFavorites, + refetchITwins, }); const noResultsText = { @@ -211,6 +213,7 @@ export const ITwinGrid = ({ isFavorite={iTwinFavorites.has(iTwin.id)} addToFavorites={addITwinToFavorites} removeFromFavorites={removeITwinFromFavorites} + refetchITwins={refetchITwins} {...tileOverrides} /> ))} diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinTile.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinTile.tsx index 2241c716..eceb8c7c 100644 --- a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinTile.tsx +++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinTile.tsx @@ -44,6 +44,8 @@ export interface ITwinTileProps { addToFavorites?(iTwinId: string): Promise; /** Function to remove the iTwin from favorites */ removeFromFavorites?(iTwinId: string): Promise; + /** Function to refetch iTwins */ + refetchITwins?: () => void; } /** @@ -58,6 +60,7 @@ export const ITwinTile = ({ isFavorite, addToFavorites, removeFromFavorites, + refetchITwins, }: ITwinTileProps) => { const strings = _mergeStrings( { @@ -71,8 +74,14 @@ export const ITwinTile = ({ ); const moreOptions = React.useMemo( - () => _buildManagedContextMenuOptions(iTwinOptions, iTwin), - [iTwinOptions, iTwin] + () => + _buildManagedContextMenuOptions( + iTwinOptions, + iTwin, + undefined, + refetchITwins + ), + [iTwinOptions, iTwin, refetchITwins] ); return ( diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts index cac36ba4..a0c02298 100644 --- a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts +++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts @@ -42,6 +42,14 @@ export const useITwinData = ({ const filteredProjects = useITwinFilter(projects, filterOptions); const [page, setPage] = React.useState(0); const [morePages, setMorePages] = React.useState(true); + + const refetchData = React.useCallback(() => { + setStatus(DataStatus.Fetching); + setProjects([]); + setPage(0); + setMorePages(true); + }, []); + const fetchMore = React.useCallback(() => { setPage((page) => page + 1); }, []); @@ -57,20 +65,21 @@ export const useITwinData = ({ morePagesRef.current || !["favorites", "recents"].includes(requestType) ) { - setStatus(DataStatus.Fetching); - setProjects([]); - setPage(0); - setMorePages(true); + refetchData(); } - }, [filterOptions, requestType]); + }, [filterOptions, requestType, refetchData]); React.useEffect(() => { // If any of the dependencies change, always restart the fetch from scratch. - setStatus(DataStatus.Fetching); - setProjects([]); - setPage(0); - setMorePages(true); - }, [accessToken, requestType, iTwinSubClass, data, serverEnvironmentPrefix]); + refetchData(); + }, [ + accessToken, + requestType, + iTwinSubClass, + data, + serverEnvironmentPrefix, + refetchData, + ]); React.useEffect(() => { if (!morePages) { @@ -165,5 +174,6 @@ export const useITwinData = ({ iTwins: filteredProjects, status, fetchMore: morePages ? fetchMore : undefined, + refetchITwins: refetchData, }; }; diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx index 2994dbc9..2a158c3c 100644 --- a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx +++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx @@ -22,6 +22,7 @@ export interface useITwinTableConfigProps { iTwinFavorites: Set; addITwinToFavorites: (iTwinId: string) => Promise; removeITwinFromFavorites: (iTwinId: string) => Promise; + refetchITwins: () => void; } export const useITwinTableConfig = ({ @@ -31,6 +32,7 @@ export const useITwinTableConfig = ({ iTwinFavorites, addITwinToFavorites, removeITwinFromFavorites, + refetchITwins, }: useITwinTableConfigProps) => { const onRowClick = (_: React.MouseEvent, row: any) => { const iTwin = row.original as ITwinFull; @@ -110,7 +112,8 @@ export const useITwinTableConfig = ({ const options = _buildManagedContextMenuOptions( iTwinActions, props.row.original, - close + close, + refetchITwins ); return options !== undefined ? options : []; }; @@ -145,6 +148,7 @@ export const useITwinTableConfig = ({ strings.tableColumnFavorites, strings.tableColumnLastModified, strings.tableColumnName, + refetchITwins, ] ); diff --git a/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx b/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx index e3594d32..b7f652d2 100644 --- a/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx +++ b/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx @@ -149,6 +149,7 @@ export const IModelGrid = ({ iModels: fetchediModels, status: fetchStatus, fetchMore, + refetchIModels, } = useIModelData({ accessToken, apiOverrides, @@ -170,6 +171,7 @@ export const IModelGrid = ({ iModelActions, onThumbnailClick, strings, + refetchIModels, }); const noResultsText = { @@ -198,6 +200,7 @@ export const IModelGrid = ({ onThumbnailClick={onThumbnailClick} apiOverrides={tileApiOverrides} useTileState={useIndividualState} + refetchIModels={refetchIModels} {...tileOverrides} /> ))} diff --git a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts index 94ed7910..5693e59a 100644 --- a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts +++ b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts @@ -67,6 +67,14 @@ export const useIModelData = ({ setPreviousSortOptions(sortOptions); } + const reset = React.useCallback(() => { + setStatus(DataStatus.Fetching); + setIModels([]); + setPage(0); + setMorePagesAvailable(true); + setNeedsUpdate(true); + }, []); + const fetchMore = React.useCallback(() => { if (status === DataStatus.Fetching || !morePagesAvailable) { return; @@ -77,11 +85,7 @@ export const useIModelData = ({ React.useEffect(() => { // start from scratch when any external state changes - setStatus(DataStatus.Fetching); - setIModels([]); - setPage(0); - setMorePagesAvailable(true); - setNeedsUpdate(true); + reset(); }, [ iTwinId, accessToken, @@ -91,6 +95,7 @@ export const useIModelData = ({ apiOverrides?.serverEnvironmentPrefix, searchText, maxCount, + reset, ]); // Main function @@ -182,6 +187,7 @@ export const useIModelData = ({ iModels: sortedIModels, status, fetchMore: morePagesAvailable ? fetchMore : undefined, + refetchIModels: reset, }; }; diff --git a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelTableConfig.tsx b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelTableConfig.tsx index 5fe39cba..ff561cc4 100644 --- a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelTableConfig.tsx +++ b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelTableConfig.tsx @@ -27,12 +27,14 @@ export interface useIModelTableConfigProps { noAuthentication: string; error: string; }; + refetchIModels: () => void; } export const useIModelTableConfig = ({ iModelActions, onThumbnailClick, strings, + refetchIModels, }: useIModelTableConfigProps) => { const onRowClick = (_: React.MouseEvent, row: any) => { const iModel = row.original as IModelFull; @@ -86,7 +88,8 @@ export const useIModelTableConfig = ({ const options = _buildManagedContextMenuOptions( iModelActions, props.row.original, - close + close, + refetchIModels ); return options !== undefined ? options : []; }; @@ -115,6 +118,7 @@ export const useIModelTableConfig = ({ strings.tableColumnDescription, strings.tableColumnLastModified, strings.tableColumnName, + refetchIModels, ] ); diff --git a/packages/modules/imodel-browser/src/containers/iModelTiles/IModelTile.tsx b/packages/modules/imodel-browser/src/containers/iModelTiles/IModelTile.tsx index 88703c39..e165d2be 100644 --- a/packages/modules/imodel-browser/src/containers/iModelTiles/IModelTile.tsx +++ b/packages/modules/imodel-browser/src/containers/iModelTiles/IModelTile.tsx @@ -27,6 +27,8 @@ export interface IModelTileProps { tileProps?: Partial; /** Object that configures different overrides for the API */ apiOverrides?: ApiOverrides; + /** Function to refetch iModels */ + refetchIModels?: () => void; } /** @@ -39,10 +41,17 @@ export const IModelTile = ({ onThumbnailClick, apiOverrides, tileProps, + refetchIModels, }: IModelTileProps) => { const moreOptions = React.useMemo( - () => _buildManagedContextMenuOptions(iModelOptions, iModel), - [iModelOptions, iModel] + () => + _buildManagedContextMenuOptions( + iModelOptions, + iModel, + undefined, + refetchIModels + ), + [iModelOptions, iModel, refetchIModels] ); const thumbnailApiOverride = apiOverrides || iModel?.thumbnail diff --git a/packages/modules/imodel-browser/src/utils/_buildMenuOptions.tsx b/packages/modules/imodel-browser/src/utils/_buildMenuOptions.tsx index c8928f7a..d6c29639 100644 --- a/packages/modules/imodel-browser/src/utils/_buildMenuOptions.tsx +++ b/packages/modules/imodel-browser/src/utils/_buildMenuOptions.tsx @@ -11,7 +11,7 @@ export interface ContextMenuBuilderItem extends Omit { key: string; visible?: boolean | ((value: T) => boolean); - onClick?: ((value?: unknown) => void) | undefined; + onClick?: ((value?: T, refetchData?: () => void) => void) | undefined; } /** Build MenuItem array for the value for each provided options @@ -20,8 +20,9 @@ export interface ContextMenuBuilderItem export const _buildManagedContextMenuOptions: ( options: ContextMenuBuilderItem[] | undefined, value: T, - closeMenu?: () => void -) => JSX.Element[] | undefined = (options, value, closeMenu) => { + closeMenu?: () => void, + refetchData?: () => void +) => JSX.Element[] | undefined = (options, value, closeMenu, refetchData) => { return options ?.filter?.(({ visible }) => { return typeof visible === "function" ? visible(value) : visible ?? true; @@ -32,7 +33,7 @@ export const _buildManagedContextMenuOptions: ( {...contextMenuProps} onClick={() => { closeMenu?.(); - onClick?.(value); + onClick?.(value, refetchData); }} key={key} value={value}