Files
frontend-app-authoring/src/library-authoring/routes.ts
Chris Chávez d6b51ecf0c feat: Add unit from library into course (#1829)
* feat: Initial worflow to add unit to course
* test: Add initial tests
* feat: Show only published units
* test: Update Subsection card test and ComponentPicker tests
* feat: Connect add unit from library API
* test: Test for Add unit from library in CourseOutline
* fix: create a new Vertical from a Library Unit
* docs: add a little note about avoiding 'vertical' where possible
* refactor: Use visibleTabs instead of showOnlyHomeTab

---------

Co-authored-by: Jillian Vogel <jill@opencraft.com>
Co-authored-by: Braden MacDonald <braden@opencraft.com>
2025-04-24 12:07:54 -07:00

172 lines
6.1 KiB
TypeScript

/**
* Constants and utility hook for the Library Authoring routes.
*/
import { useCallback } from 'react';
import {
generatePath,
matchPath,
useParams,
useLocation,
useNavigate,
useSearchParams,
type PathMatch,
} from 'react-router-dom';
export const BASE_ROUTE = '/library/:libraryId';
export const ROUTES = {
// LibraryAuthoringPage routes:
// * Components tab, with an optionally selected componentId in the sidebar.
COMPONENTS: '/components/:componentId?',
// * Collections tab, with an optionally selected collectionId in the sidebar.
COLLECTIONS: '/collections/:collectionId?',
// * Units tab, with an optionally selected unitId in the sidebar.
UNITS: '/units/:unitId?',
// * All Content tab, with an optionally selected componentId in the sidebar.
COMPONENT: '/component/:componentId',
// * All Content tab, with an optionally selected collection or unit in the sidebar.
HOME: '/:selectedItemId?',
// LibraryCollectionPage route:
// * with a selected collectionId and/or an optionally selected componentId.
COLLECTION: '/collection/:collectionId/:componentId?',
// LibraryUnitPage route:
// * with a selected unitId and/or an optionally selected componentId.
UNIT: '/unit/:unitId/:componentId?',
};
export enum ContentType {
home = '',
collections = 'collections',
components = 'components',
units = 'units',
}
export const allLibraryPageTabs: ContentType[] = Object.values(ContentType);
export type NavigateToData = {
componentId?: string,
collectionId?: string,
contentType?: ContentType,
unitId?: string,
};
export type LibraryRoutesData = {
insideCollection: PathMatch<string> | null;
insideCollections: PathMatch<string> | null;
insideComponents: PathMatch<string> | null;
insideUnits: PathMatch<string> | null;
insideUnit: PathMatch<string> | null;
// Navigate using the best route from the current location for the given parameters.
navigateTo: (dict?: NavigateToData) => void;
};
export const useLibraryRoutes = (): LibraryRoutesData => {
const { pathname } = useLocation();
const params = useParams();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const insideCollection = matchPath(BASE_ROUTE + ROUTES.COLLECTION, pathname);
const insideCollections = matchPath(BASE_ROUTE + ROUTES.COLLECTIONS, pathname);
const insideComponents = matchPath(BASE_ROUTE + ROUTES.COMPONENTS, pathname);
const insideUnits = matchPath(BASE_ROUTE + ROUTES.UNITS, pathname);
const insideUnit = matchPath(BASE_ROUTE + ROUTES.UNIT, pathname);
const navigateTo = useCallback(({
componentId,
collectionId,
unitId,
contentType,
}: NavigateToData = {}) => {
const {
collectionId: urlCollectionId,
componentId: urlComponentId,
unitId: urlUnitId,
selectedItemId: urlSelectedItemId,
} = params;
const routeParams = {
...params,
// Overwrite the current componentId/collectionId params if provided
...((componentId !== undefined) && { componentId }),
...((collectionId !== undefined) && { collectionId, selectedItemId: collectionId }),
...((unitId !== undefined) && { unitId, selectedItemId: unitId }),
...(contentType === ContentType.home && { selectedItemId: urlCollectionId || urlUnitId }),
...(contentType === ContentType.components && { componentId: urlComponentId || urlSelectedItemId }),
...(contentType === ContentType.collections && { collectionId: urlCollectionId || urlSelectedItemId }),
...(contentType === ContentType.units && { unitId: urlUnitId || urlSelectedItemId }),
};
let route: string;
// Providing contentType overrides the current route so we can change tabs.
if (contentType === ContentType.components) {
route = ROUTES.COMPONENTS;
} else if (contentType === ContentType.collections) {
route = ROUTES.COLLECTIONS;
} else if (contentType === ContentType.units) {
route = ROUTES.UNITS;
} else if (contentType === ContentType.home) {
route = ROUTES.HOME;
} else if (insideCollections) {
// We're inside the Collections tab,
route = (
(collectionId && collectionId === (urlCollectionId || urlSelectedItemId))
// now open the previously-selected collection,
? ROUTES.COLLECTION
// or stay there to list all collections, or a selected collection.
: ROUTES.COLLECTIONS
);
} else if (insideCollection) {
// We're viewing a Collection, so stay there,
// and optionally select a component in that collection.
route = ROUTES.COLLECTION;
} else if (insideComponents) {
// We're inside the Components tab, so stay there,
// optionally selecting a component.
route = ROUTES.COMPONENTS;
} else if (insideUnits) {
// We're inside the units tab,
route = (
(unitId && unitId === (urlUnitId || urlSelectedItemId))
// now open the previously-selected unit,
? ROUTES.UNIT
// or stay there to list all units, or a selected unit.
: ROUTES.UNITS
);
} else if (insideUnit) {
// We're viewing a Unit, so stay there,
// and optionally select a component in that Unit.
route = ROUTES.UNIT;
} else if (componentId) {
// We're inside the All Content tab, so stay there,
// and select a component.
route = ROUTES.COMPONENT;
} else if (collectionId && collectionId === (urlCollectionId || urlSelectedItemId)) {
// now open the previously-selected collection
route = ROUTES.COLLECTION;
} else if (unitId && unitId === (urlUnitId || urlSelectedItemId)) {
// now open the previously-selected unit
route = ROUTES.UNIT;
} else {
// or stay there to list all content, or optionally select a collection.
route = ROUTES.HOME;
}
const newPath = generatePath(BASE_ROUTE + route, routeParams);
navigate({
pathname: newPath,
search: searchParams.toString(),
});
}, [navigate, params, searchParams, pathname]);
return {
navigateTo,
insideCollection,
insideCollections,
insideComponents,
insideUnits,
insideUnit,
};
};