386 lines
12 KiB
TypeScript
386 lines
12 KiB
TypeScript
import React from "react";
|
|
import { matchPath } from "react-router-dom";
|
|
|
|
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine/es";
|
|
|
|
// export interface RouteDefinition {
|
|
// key: string,
|
|
// type: string,
|
|
// path: string,
|
|
// Component: any,
|
|
// checkCanAccess: (routeDef:any, state:any) => boolean,
|
|
// }
|
|
import config from "~/config";
|
|
import {
|
|
BuilderMethod,
|
|
NavigationType,
|
|
RouteKey,
|
|
SectionKey,
|
|
} from "~/subApps/builder/constants";
|
|
import { ClassChoose } from "~/subApps/builder/routes/ClassChoose";
|
|
import { Home } from "~/subApps/builder/routes/Home";
|
|
import { SpeciesChoose } from "~/subApps/builder/routes/SpeciesChoose";
|
|
|
|
import { WhatsNext } from "../../../../subApps/builder/routes/WhatsNext";
|
|
import AbilityScoresHelp from "../containers/pages/AbilityScoresHelp";
|
|
import AbilityScoresManage from "../containers/pages/AbilityScoresManage";
|
|
import ClassHelp from "../containers/pages/ClassHelp";
|
|
import ClassesManage from "../containers/pages/ClassesManage";
|
|
import DescriptionHelp from "../containers/pages/DescriptionHelp";
|
|
import DescriptionManage from "../containers/pages/DescriptionManage";
|
|
import EquipmentHelp from "../containers/pages/EquipmentHelp";
|
|
import EquipmentManage from "../containers/pages/EquipmentManage";
|
|
import HomeHelp from "../containers/pages/HomeHelp";
|
|
import SpeciesHelp from "../containers/pages/SpeciesHelp";
|
|
import SpeciesManage from "../containers/pages/SpeciesManage";
|
|
import { NavigationUtils } from "../utils";
|
|
|
|
// TODO: remove the use of either `Component` or `getComponent` on the ROUTE_DEFINITIONS and we can get rid of the no-dd-sa rule..
|
|
|
|
const BASE_PATHNAME = config.basePathname;
|
|
|
|
export const ROUTE_DEFINITIONS = {
|
|
[RouteKey.HOME_BASIC_INFO]: {
|
|
key: RouteKey.HOME_BASIC_INFO,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/home/basic`,
|
|
Component: Home,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <Home />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.RACE_CHOOSE]: {
|
|
key: RouteKey.RACE_CHOOSE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/species/choose`,
|
|
Component: SpeciesChoose,
|
|
getComponent: () => <SpeciesChoose />,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
checkCanAccess: (routeDef, state) => {
|
|
if (!NavigationUtils.checkStdBuilderPageRequirements(routeDef, state)) {
|
|
return false;
|
|
}
|
|
|
|
if (rulesEngineSelectors.getRace(state) !== null) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
[RouteKey.RACE_MANAGE]: {
|
|
key: RouteKey.RACE_MANAGE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/species/manage`,
|
|
Component: SpeciesManage,
|
|
getComponent: () => <SpeciesManage />,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
checkCanAccess: (routeDef, state) => {
|
|
if (!NavigationUtils.checkStdBuilderPageRequirements(routeDef, state)) {
|
|
return false;
|
|
}
|
|
|
|
if (rulesEngineSelectors.getRace(state) === null) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
[RouteKey.CLASS_CHOOSE]: {
|
|
key: RouteKey.CLASS_CHOOSE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/class/choose`,
|
|
Component: ClassChoose,
|
|
getComponent: () => <ClassChoose />,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
checkCanAccess: (routeDef, state) => {
|
|
if (!NavigationUtils.checkStdBuilderPageRequirements(routeDef, state)) {
|
|
return false;
|
|
}
|
|
|
|
if (rulesEngineSelectors.getClasses(state).length > 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
[RouteKey.CLASS_MANAGE]: {
|
|
key: RouteKey.CLASS_MANAGE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/class/manage`,
|
|
Component: ClassesManage,
|
|
getComponent: () => <ClassesManage />,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
checkCanAccess: (routeDef, state) => {
|
|
if (!NavigationUtils.checkStdBuilderPageRequirements(routeDef, state)) {
|
|
return false;
|
|
}
|
|
|
|
if (rulesEngineSelectors.getClasses(state).length === 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
[RouteKey.ABILITY_SCORES_MANAGE]: {
|
|
key: RouteKey.ABILITY_SCORES_MANAGE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/ability-scores/manage`,
|
|
Component: AbilityScoresManage,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <AbilityScoresManage />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.DESCRIPTION_MANAGE]: {
|
|
key: RouteKey.DESCRIPTION_MANAGE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/description/manage`,
|
|
Component: DescriptionManage,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <DescriptionManage />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.EQUIPMENT_MANAGE]: {
|
|
key: RouteKey.EQUIPMENT_MANAGE,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/equipment/manage`,
|
|
Component: EquipmentManage,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <EquipmentManage />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
|
|
[RouteKey.WHATS_NEXT]: {
|
|
key: RouteKey.WHATS_NEXT,
|
|
type: NavigationType.Page,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/whats-next`,
|
|
Component: WhatsNext,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <WhatsNext />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
|
|
[RouteKey.HOME_HELP]: {
|
|
key: RouteKey.HOME_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/home/help`,
|
|
Component: HomeHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <HomeHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.RACE_HELP]: {
|
|
key: RouteKey.RACE_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/species/help`,
|
|
Component: SpeciesHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <SpeciesHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.CLASS_HELP]: {
|
|
key: RouteKey.CLASS_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/class/help`,
|
|
Component: ClassHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <ClassHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.ABILITY_SCORES_HELP]: {
|
|
key: RouteKey.ABILITY_SCORES_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/ability-scores/help`,
|
|
Component: AbilityScoresHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <AbilityScoresHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.DESCRIPTION_HELP]: {
|
|
key: RouteKey.DESCRIPTION_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/description/help`,
|
|
Component: DescriptionHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <DescriptionHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
[RouteKey.EQUIPMENT_HELP]: {
|
|
key: RouteKey.EQUIPMENT_HELP,
|
|
type: NavigationType.HelpPage,
|
|
path: `${BASE_PATHNAME}/:characterId/builder/equipment/help`,
|
|
Component: EquipmentHelp,
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => <EquipmentHelp />,
|
|
checkCanAccess: (routeDef, state) =>
|
|
NavigationUtils.checkStdBuilderPageRequirements(routeDef, state),
|
|
},
|
|
};
|
|
|
|
export interface RouteDef {
|
|
key: string; // get the thing as an enum
|
|
type: string;
|
|
path: string;
|
|
Component: React.ComponentType<any>;
|
|
// no-dd-sa:typescript-best-practices/no-dupe-keys
|
|
getComponent: () => React.ReactElement;
|
|
checkCanAccess?: (routeDef: RouteDef, state: any) => boolean;
|
|
}
|
|
|
|
export const getAllRouterRoutes = () => {
|
|
return (Object.values(ROUTE_DEFINITIONS) as RouteDef[]).map((routeDef) => ({
|
|
element: <routeDef.Component />,
|
|
path: routeDef.path,
|
|
routeDef,
|
|
}));
|
|
};
|
|
|
|
const addSearchParamsToRouteDef = (routeDef: RouteDef): RouteDef => {
|
|
let params = "";
|
|
if (typeof window !== "undefined") {
|
|
// If we ever need to know specific search params, we can use URLSearchParams here instead.
|
|
params = window.location.search;
|
|
}
|
|
return {
|
|
...routeDef,
|
|
path: `${routeDef.path}${params}`,
|
|
};
|
|
};
|
|
|
|
export const getRouteDef = (routeKey) => ROUTE_DEFINITIONS[routeKey];
|
|
export const getRouteDefPath = (routeKey) => {
|
|
const basePath = getRouteDef(routeKey);
|
|
return addSearchParamsToRouteDef(basePath).path;
|
|
};
|
|
export const getRouteDefType = (routeKey) => getRouteDef(routeKey).type;
|
|
|
|
export const NAVIGATION_DEFINITIONS = {
|
|
[BuilderMethod.STEP_BY_STEP]: [
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.HOME,
|
|
getLabel: () => "Home",
|
|
routes: [
|
|
getRouteDef(RouteKey.HOME_HELP),
|
|
getRouteDef(RouteKey.HOME_BASIC_INFO),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.CLASS,
|
|
getLabel: () => "1. Class",
|
|
routes: [
|
|
getRouteDef(RouteKey.CLASS_HELP),
|
|
getRouteDef(RouteKey.CLASS_CHOOSE),
|
|
getRouteDef(RouteKey.CLASS_MANAGE),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.DESCRIPTION,
|
|
getLabel: () => "2. Background",
|
|
routes: [
|
|
getRouteDef(RouteKey.DESCRIPTION_HELP),
|
|
getRouteDef(RouteKey.DESCRIPTION_MANAGE),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.RACE,
|
|
getLabel: () => "3. Species",
|
|
routes: [
|
|
getRouteDef(RouteKey.RACE_HELP),
|
|
getRouteDef(RouteKey.RACE_CHOOSE),
|
|
getRouteDef(RouteKey.RACE_MANAGE),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.ABILITY_SCORES,
|
|
getLabel: () => "4. Abilities",
|
|
routes: [
|
|
getRouteDef(RouteKey.ABILITY_SCORES_HELP),
|
|
getRouteDef(RouteKey.ABILITY_SCORES_MANAGE),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.EQUIPMENT,
|
|
getLabel: () => "5. Equipment",
|
|
routes: [
|
|
getRouteDef(RouteKey.EQUIPMENT_HELP),
|
|
getRouteDef(RouteKey.EQUIPMENT_MANAGE),
|
|
],
|
|
},
|
|
{
|
|
type: NavigationType.Section,
|
|
key: SectionKey.WHATS_NEXT,
|
|
getLabel: () => "What's Next",
|
|
routes: [getRouteDef(RouteKey.WHATS_NEXT)],
|
|
},
|
|
],
|
|
};
|
|
|
|
export function checkIsRouteInSection(testRouteDef, sectionRoutes) {
|
|
if (testRouteDef && sectionRoutes) {
|
|
return !!sectionRoutes.filter(
|
|
(routeDef) => routeDef.key === testRouteDef.key
|
|
).length;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function getNavigationSections(builderMethod, currentRouteDef) {
|
|
const navConfig = getNavBuilderConfig(builderMethod);
|
|
|
|
return navConfig.map((section) => ({
|
|
...section,
|
|
active: checkIsRouteInSection(currentRouteDef, section.routes),
|
|
}));
|
|
}
|
|
|
|
export function getNavBuilderConfig(builderMethod) {
|
|
if (builderMethod) {
|
|
const config = NAVIGATION_DEFINITIONS[builderMethod];
|
|
|
|
// Add search params to all route paths in the config
|
|
return config.map((section) => ({
|
|
...section,
|
|
routes: section.routes.map((routeDef) =>
|
|
addSearchParamsToRouteDef(routeDef)
|
|
),
|
|
}));
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function getCurrentRouteDef(currentPath) {
|
|
const routes = getAllRouterRoutes();
|
|
|
|
let matchedRouteDef: RouteDef | null = null;
|
|
routes.forEach((route) => {
|
|
const match = matchPath(route.path, currentPath);
|
|
|
|
if (match) {
|
|
matchedRouteDef = route.routeDef;
|
|
}
|
|
});
|
|
|
|
return matchedRouteDef;
|
|
}
|