Skip to content

Commit 9accc83

Browse files
committed
feat: integrate CollectionIndexProvider for optimized collection indexing
1 parent 496c5da commit 9accc83

4 files changed

Lines changed: 95 additions & 55 deletions

File tree

src/library-authoring/LibraryAuthoringPage.tsx

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
} from '../search-manager';
3939
import LibraryContent from './LibraryContent';
4040
import { LibrarySidebar } from './library-sidebar';
41-
import { useComponentPickerContext } from './common/context/ComponentPickerContext';
41+
import { useComponentPickerContext, CollectionIndexProvider } from './common/context/ComponentPickerContext';
4242
import { useLibraryContext } from './common/context/LibraryContext';
4343
import { SidebarBodyComponentId, useSidebarContext } from './common/context/SidebarContext';
4444
import { allLibraryPageTabs, ContentType, useLibraryRoutes } from './routes';
@@ -284,38 +284,40 @@ const LibraryAuthoringPage = ({
284284
extraFilter={extraFilter}
285285
overrideTypesFilter={overrideTypesFilter}
286286
>
287-
<SubHeader
288-
title={<SubHeaderTitle title={libraryData.title} />}
289-
subtitle={!componentPickerMode ? intl.formatMessage(messages.headingSubtitle) : undefined}
290-
breadcrumbs={breadcumbs}
291-
headerActions={<HeaderActions />}
292-
hideBorder
293-
/>
294-
<Tabs
295-
variant="tabs"
296-
activeKey={activeKey}
297-
onSelect={handleTabChange}
298-
className="my-3"
299-
>
300-
{visibleTabsToRender}
301-
</Tabs>
302-
<ActionRow className="my-3">
303-
<SearchKeywordsField className="mr-3" />
304-
<FilterByTags />
305-
{!(insideCollections || insideUnits) && <FilterByBlockType />}
306-
<LibraryFilterByPublished key={
307-
// It is necessary to re-render `LibraryFilterByPublished` every time `FilterByBlockType`
308-
// appears or disappears, this is because when the menu is opened it is rendered
309-
// in a previous state, causing an inconsistency in its position.
310-
// By changing the key we can re-render the component.
311-
!(insideCollections || insideUnits) ? 'filter-published-1' : 'filter-published-2'
312-
}
287+
<CollectionIndexProvider>
288+
<SubHeader
289+
title={<SubHeaderTitle title={libraryData.title} />}
290+
subtitle={!componentPickerMode ? intl.formatMessage(messages.headingSubtitle) : undefined}
291+
breadcrumbs={breadcumbs}
292+
headerActions={<HeaderActions />}
293+
hideBorder
313294
/>
314-
<ClearFiltersButton />
315-
<ActionRow.Spacer />
316-
<SearchSortWidget />
317-
</ActionRow>
318-
<LibraryContent contentType={activeKey} />
295+
<Tabs
296+
variant="tabs"
297+
activeKey={activeKey}
298+
onSelect={handleTabChange}
299+
className="my-3"
300+
>
301+
{visibleTabsToRender}
302+
</Tabs>
303+
<ActionRow className="my-3">
304+
<SearchKeywordsField className="mr-3" />
305+
<FilterByTags />
306+
{!(insideCollections || insideUnits) && <FilterByBlockType />}
307+
<LibraryFilterByPublished key={
308+
// It is necessary to re-render `LibraryFilterByPublished` every time `FilterByBlockType`
309+
// appears or disappears, this is because when the menu is opened it is rendered
310+
// in a previous state, causing an inconsistency in its position.
311+
// By changing the key we can re-render the component.
312+
!(insideCollections || insideUnits) ? 'filter-published-1' : 'filter-published-2'
313+
}
314+
/>
315+
<ClearFiltersButton />
316+
<ActionRow.Spacer />
317+
<SearchSortWidget />
318+
</ActionRow>
319+
<LibraryContent contentType={activeKey} />
320+
</CollectionIndexProvider>
319321
</SearchContextProvider>
320322
</Container>
321323
{!componentPickerMode && <StudioFooterSlot containerProps={{ size: undefined }} />}

src/library-authoring/collections/LibraryCollectionPage.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
} from '../../search-manager';
3030
import { SubHeaderTitle } from '../LibraryAuthoringPage';
3131
import { useCollection, useContentLibrary } from '../data/apiHooks';
32-
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
32+
import { useComponentPickerContext, CollectionIndexProvider } from '../common/context/ComponentPickerContext';
3333
import { useLibraryContext } from '../common/context/LibraryContext';
3434
import { SidebarBodyComponentId, useSidebarContext } from '../common/context/SidebarContext';
3535
import messages from './messages';
@@ -208,22 +208,24 @@ const LibraryCollectionPage = () => {
208208
<SearchContextProvider
209209
extraFilter={extraFilter}
210210
>
211-
<SubHeader
212-
title={<SubHeaderTitle title={collectionData.title} />}
213-
breadcrumbs={breadcrumbs}
214-
headerActions={<HeaderActions />}
215-
hideBorder
216-
/>
217-
<ActionRow className="my-3">
218-
<SearchKeywordsField className="mr-3" />
219-
<FilterByTags />
220-
<FilterByBlockType />
221-
<LibraryFilterByPublished />
222-
<ClearFiltersButton />
223-
<ActionRow.Spacer />
224-
<SearchSortWidget />
225-
</ActionRow>
226-
<LibraryCollectionComponents />
211+
<CollectionIndexProvider>
212+
<SubHeader
213+
title={<SubHeaderTitle title={collectionData.title} />}
214+
breadcrumbs={breadcrumbs}
215+
headerActions={<HeaderActions />}
216+
hideBorder
217+
/>
218+
<ActionRow className="my-3">
219+
<SearchKeywordsField className="mr-3" />
220+
<FilterByTags />
221+
<FilterByBlockType />
222+
<LibraryFilterByPublished />
223+
<ClearFiltersButton />
224+
<ActionRow.Spacer />
225+
<SearchSortWidget />
226+
</ActionRow>
227+
<LibraryCollectionComponents />
228+
</CollectionIndexProvider>
227229
</SearchContextProvider>
228230
</Container>
229231
{!componentPickerMode && <StudioFooterSlot containerProps={{ size: undefined }} />}

src/library-authoring/common/context/ComponentPickerContext.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import {
55
useMemo,
66
useState,
77
} from 'react';
8-
import { ContentHit } from 'search-manager';
9-
import { useSearchContext } from 'search-manager/SearchManager';
8+
import { ContentHit, useSearchContext } from 'search-manager';
9+
10+
/**
11+
* Provides pre-computed collection indexing data to avoid repeated computation.
12+
*/
13+
const CollectionIndexContext = createContext<CollectionIndexData | undefined>(undefined);
1014

1115
export interface SelectedComponent {
1216
usageKey: string;
@@ -176,6 +180,40 @@ export const useCollectionIndexing = (
176180
};
177181
}, [hits]);
178182

183+
type CollectionIndexProviderProps = {
184+
children: React.ReactNode;
185+
};
186+
187+
/**
188+
* React component to provide pre-computed collection indexing data.
189+
* Must be rendered inside a SearchContextProvider.
190+
* This provider ensures collection indexing is computed once and shared
191+
* with all AddComponentWidget instances, avoiding O(n*m) complexity.
192+
*/
193+
export const CollectionIndexProvider = ({ children }: CollectionIndexProviderProps) => {
194+
const { hits } = useSearchContext();
195+
const collectionIndexData = useCollectionIndexing(hits);
196+
197+
return (
198+
<CollectionIndexContext.Provider value={collectionIndexData}>
199+
{children}
200+
</CollectionIndexContext.Provider>
201+
);
202+
};
203+
204+
/**
205+
* Hook to access pre-computed collection indexing data.
206+
* Must be used within a CollectionIndexProvider.
207+
* @returns CollectionIndexData with pre-computed maps
208+
*/
209+
export const useCollectionIndexContext = (): CollectionIndexData => {
210+
const ctx = useContext(CollectionIndexContext);
211+
if (ctx === undefined) {
212+
throw new Error('useCollectionIndexContext must be used within a CollectionIndexProvider');
213+
}
214+
return ctx;
215+
};
216+
179217
/**
180218
* React component to provide `ComponentPickerContext`
181219
*/

src/library-authoring/components/AddComponentWidget.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import {
88
CheckBoxOutlineBlank,
99
} from '@openedx/paragon/icons';
1010

11-
import { useSearchContext } from '../../search-manager';
1211
import {
1312
SelectedComponent,
1413
useComponentPickerContext,
15-
useCollectionIndexing,
14+
useCollectionIndexContext,
1615
} from '../common/context/ComponentPickerContext';
1716

1817
import messages from './messages';
@@ -38,13 +37,12 @@ const AddComponentWidget = ({
3837
selectedCollections,
3938
} = useComponentPickerContext();
4039

41-
const { hits } = useSearchContext();
4240
const {
4341
collectionToComponents,
4442
componentToCollections,
4543
collectionToAffectedSizes,
4644
collectionSizes,
47-
} = useCollectionIndexing(hits);
45+
} = useCollectionIndexContext();
4846

4947
const collectionData = useMemo(() => {
5048
if (isCollection) {

0 commit comments

Comments
 (0)