Repository navigation
feat: refactor library routes and add section/subsection tabs #2039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c6509a9
950914d
312ed20
9144414
3ccddc8
636653d
a4a6d99
3adac7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| import { | ||
| type ReactNode, | ||
| useCallback, | ||
| useEffect, | ||
| useState, | ||
| } from 'react'; | ||
| import { Helmet } from 'react-helmet'; | ||
|
|
@@ -72,8 +71,8 @@ const HeaderActions = () => { | |
| } | ||
|
|
||
| if (!componentPickerMode) { | ||
| // Reset URL to library home | ||
| navigateTo({ componentId: '', collectionId: '', unitId: '' }); | ||
| // If not in component picker mode, reset selected item when opening the info sidebar | ||
| navigateTo({ selectedItemId: '' }); | ||
| } | ||
| }, [navigateTo, sidebarComponentInfo, closeLibrarySidebar, openLibrarySidebar]); | ||
|
|
||
|
|
@@ -151,16 +150,15 @@ const LibraryAuthoringPage = ({ | |
| isLoadingLibraryData, | ||
| showOnlyPublished, | ||
| extraFilter: contextExtraFilter, | ||
| componentId, | ||
| collectionId, | ||
| unitId, | ||
| } = useLibraryContext(); | ||
| const { openInfoSidebar, sidebarComponentInfo } = useSidebarContext(); | ||
| const { sidebarComponentInfo } = useSidebarContext(); | ||
|
|
||
| const { | ||
| insideCollections, | ||
| insideComponents, | ||
| insideUnits, | ||
| insideSections, | ||
| insideSubsections, | ||
| navigateTo, | ||
| } = useLibraryRoutes(); | ||
|
|
||
|
|
@@ -178,16 +176,16 @@ const LibraryAuthoringPage = ({ | |
| if (insideUnits) { | ||
| return ContentType.units; | ||
| } | ||
| if (insideSubsections) { | ||
| return ContentType.subsections; | ||
| } | ||
| if (insideSections) { | ||
| return ContentType.sections; | ||
| } | ||
| return ContentType.home; | ||
| }; | ||
| const [activeKey, setActiveKey] = useState<ContentType>(getActiveKey); | ||
|
|
||
| useEffect(() => { | ||
| if (!componentPickerMode) { | ||
| openInfoSidebar(componentId, collectionId, unitId); | ||
| } | ||
| }, []); | ||
|
|
||
|
Comment on lines
-185
to
-190
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to SidebarContext |
||
| if (isLoadingLibraryData) { | ||
| return <Loading />; | ||
| } | ||
|
|
@@ -241,31 +239,28 @@ const LibraryAuthoringPage = ({ | |
| components: 'type = "library_block"', | ||
| collections: 'type = "collection"', | ||
| units: 'block_type = "unit"', | ||
| subsections: 'block_type = "subsection"', | ||
| sections: 'block_type = "section"', | ||
| }; | ||
| if (activeKey !== ContentType.home) { | ||
| extraFilter.push(activeTypeFilters[activeKey]); | ||
| } | ||
|
|
||
| /* | ||
|
|
||
| <FilterByPublished key={ | ||
| // It is necessary to re-render `FilterByPublished` every time `FilterByBlockType` | ||
| // appears or disappears, this is because when the menu is opened it is rendered | ||
| // in a previous state, causing an inconsistency in its position. | ||
| // By changing the key we can re-render the component. | ||
| !(insideCollections || insideUnits) ? 'filter-published-1' : 'filter-published-2' | ||
| } | ||
| */ | ||
|
|
||
|
Comment on lines
-249
to
-259
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be a temporary/not used code commented |
||
| // Disable filtering by block/problem type when viewing the Collections tab. | ||
| const overrideTypesFilter = (insideCollections || insideUnits) ? new TypesFilterData() : undefined; | ||
| // Disable filtering by block/problem type when viewing the Collections/Units/Sections/Subsections tab. | ||
| const onlyOneType = (insideCollections || insideUnits || insideSections || insideSubsections); | ||
| const overrideTypesFilter = onlyOneType | ||
| ? new TypesFilterData() | ||
| : undefined; | ||
|
|
||
| const tabTitles = { | ||
| [ContentType.home]: intl.formatMessage(messages.homeTab), | ||
| [ContentType.collections]: intl.formatMessage(messages.collectionsTab), | ||
| [ContentType.components]: intl.formatMessage(messages.componentsTab), | ||
| [ContentType.units]: intl.formatMessage(messages.unitsTab), | ||
| [ContentType.subsections]: intl.formatMessage(messages.subsectionsTab), | ||
| [ContentType.sections]: intl.formatMessage(messages.sectionsTab), | ||
| }; | ||
|
|
||
| const visibleTabsToRender = visibleTabs.map((contentType) => ( | ||
| <Tab key={contentType} eventKey={contentType} title={tabTitles[contentType]} /> | ||
| )); | ||
|
|
@@ -309,7 +304,7 @@ const LibraryAuthoringPage = ({ | |
| <ActionRow className="my-3"> | ||
| <SearchKeywordsField className="mr-3" /> | ||
| <FilterByTags /> | ||
| {!(insideCollections || insideUnits) && <FilterByBlockType />} | ||
| {!(onlyOneType) && <FilterByBlockType />} | ||
| <LibraryFilterByPublished key={ | ||
| // It is necessary to re-render `LibraryFilterByPublished` every time `FilterByBlockType` | ||
| // appears or disappears, this is because when the menu is opened it is rendered | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,12 @@ | ||
| import { useCallback } from 'react'; | ||
| import React from 'react'; | ||
| import { | ||
| Outlet, | ||
| Route, | ||
| Routes, | ||
| useMatch, | ||
| useParams, | ||
| type PathMatch, | ||
| } from 'react-router-dom'; | ||
|
|
||
| import { BASE_ROUTE, ROUTES } from './routes'; | ||
| import { ROUTES } from './routes'; | ||
| import LibraryAuthoringPage from './LibraryAuthoringPage'; | ||
| import { LibraryProvider } from './common/context/LibraryContext'; | ||
| import { SidebarProvider } from './common/context/SidebarContext'; | ||
|
|
@@ -18,70 +17,63 @@ import { ComponentPicker } from './component-picker'; | |
| import { ComponentEditorModal } from './components/ComponentEditorModal'; | ||
| import { LibraryUnitPage } from './units'; | ||
|
|
||
| const LibraryLayout = () => { | ||
| const { libraryId } = useParams(); | ||
| const LibraryLayoutWrapper: React.FC<React.PropsWithChildren> = ({ children }) => { | ||
| const { libraryId, collectionId, unitId } = useParams(); | ||
|
|
||
| if (libraryId === undefined) { | ||
| // istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker. | ||
| throw new Error('Error: route is missing libraryId.'); | ||
| } | ||
|
|
||
| // The top-level route is `${BASE_ROUTE}/*`, so match will always be non-null. | ||
| const matchCollection = useMatch(`${BASE_ROUTE}${ROUTES.COLLECTION}`) as PathMatch<'libraryId' | 'collectionId'> | null; | ||
| const collectionId = matchCollection?.params.collectionId; | ||
|
|
||
| // The top-level route is `${BASE_ROUTE}/*`, so match will always be non-null. | ||
| const matchUnit = useMatch(`${BASE_ROUTE}${ROUTES.UNIT}`) as PathMatch<'libraryId' | 'unitId'> | null; | ||
| const unitId = matchUnit?.params.unitId; | ||
|
|
||
| const context = useCallback((childPage) => ( | ||
| return ( | ||
| <LibraryProvider | ||
| /** We need to pass the collectionId or unitId as key to the LibraryProvider to force a re-render | ||
| * when we navigate to a collection or unit page. */ | ||
| /** NOTE: We need to pass the collectionId or unitId as key to the LibraryProvider to force a re-render | ||
| * when we navigate to a collection or unit page. This is necessary to make the back/forward navigation | ||
| * work correctly, as the LibraryProvider needs to rebuild the state from the URL. | ||
| * */ | ||
| key={collectionId || unitId} | ||
| libraryId={libraryId} | ||
| /** The component picker modal to use. We need to pass it as a reference instead of | ||
| /** NOTE: The component picker modal to use. We need to pass it as a reference instead of | ||
| * directly importing it to avoid the import cycle: | ||
| * ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage > | ||
| * Sidebar > AddContent > ComponentPicker */ | ||
| componentPicker={ComponentPicker} | ||
| > | ||
| <SidebarProvider> | ||
| <> | ||
| {childPage} | ||
| <CreateCollectionModal /> | ||
| <CreateContainerModal /> | ||
| <ComponentEditorModal /> | ||
| </> | ||
| {children ?? <Outlet />} | ||
| <CreateCollectionModal /> | ||
| <CreateContainerModal /> | ||
| <ComponentEditorModal /> | ||
| </SidebarProvider> | ||
| </LibraryProvider> | ||
| ), [collectionId, unitId]); | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <Routes> | ||
| const LibraryLayout = () => ( | ||
| <Routes> | ||
| <Route element={<LibraryLayoutWrapper />}> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the |
||
| {[ | ||
| ROUTES.HOME, | ||
| ROUTES.COMPONENT, | ||
| ROUTES.COMPONENTS, | ||
| ROUTES.COLLECTIONS, | ||
| ROUTES.UNITS, | ||
| ].map((route) => ( | ||
| <Route | ||
| key={route} | ||
| path={route} | ||
| element={context(<LibraryAuthoringPage />)} | ||
| Component={LibraryAuthoringPage} | ||
| /> | ||
| ))} | ||
| <Route | ||
| path={ROUTES.COLLECTION} | ||
| element={context(<LibraryCollectionPage />)} | ||
| Component={LibraryCollectionPage} | ||
| /> | ||
| <Route | ||
| path={ROUTES.UNIT} | ||
| element={context(<LibraryUnitPage />)} | ||
| Component={LibraryUnitPage} | ||
| /> | ||
| </Routes> | ||
| ); | ||
| }; | ||
| </Route> | ||
| </Routes> | ||
| ); | ||
|
|
||
| export default LibraryLayout; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, shouldn't
visibleTabsbe added like this? I'm not sure where this case is used to see the Component PickerThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! That makes sense!
Fixed: a4a6d99
Note: This current code is used on the legacy studio, where an iframe opens the route
http://apps.local.edly.io:2001/authoring/component-pickerto select components for courses.