New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
+161 -91
View File
@@ -1,19 +1,35 @@
import { useQuery } from "react-query";
import { useMatch } from "react-router-dom";
import { Routes as Router, Route, Navigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { isEmpty } from "lodash";
import { JSX } from "react";
import { Provider, useSelector } from "react-redux";
import {
Navigate,
Outlet,
Route,
Routes as Router,
useMatch,
useParams,
} from "react-router-dom";
import config from "../config";
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine/es";
import { GameLogContextProvider } from "@dndbeyond/game-log-components";
import { PddContextProvider } from "@dndbeyond/pocket-dimension-dice/components/PddContextProvider";
import { Context as DiceWorkerContext } from "@dndbeyond/pocket-dimension-dice/context";
import configureSheetStore from "~/tools/js/CharacterSheet/store/configureStore";
import config, { authEndpoint } from "../config";
import { useAuth } from "../contexts/Authentication";
import { getCharacters } from "../helpers/characterServiceApi";
import type { AppUserState } from "../hooks/useUser";
import { getStatus } from "../state/selectors/characterUtils";
import { MyCharacters } from "../subApps/listing";
import { getUrlWithParams } from "../tools/js/CharacterBuilder/utils/navigationUtils";
import { CharacterStatusEnum, QueryResponseData } from "../types";
import { CharacterStatusEnum } from "../types";
import MaxCharactersMessage from "./max-characters-message";
import SheetBuilderApp from "./sheet-builder-app";
const BASE_PATHNAME = config.basePathname;
const store = configureSheetStore();
/**
*
@@ -60,13 +76,13 @@ function GetCallCharacters({
}): JSX.Element {
const isCharactersListing = useMatch({ path: BASE_PATHNAME, end: true });
const { isLoading, data, refetch, error } = useQuery<
QueryResponseData,
Error
>("repoData", () => getCharacters(), {
const { isLoading, data, refetch, error } = useQuery({
queryKey: ["repoData"],
queryFn: () => getCharacters(),
refetchOnWindowFocus: false,
refetchInterval: 24 * 60 * 60 * 1000,
});
if (isLoading) {
return <>{null}</>;
}
@@ -80,94 +96,148 @@ function GetCallCharacters({
return <>{children({ isLoading, data, refetch, error })}</>;
}
type AppRoute = {
path: string;
element: JSX.Element;
};
function CharacterSheetLayout() {
return (
<Provider store={store}>
<CharacterSheetProviders />
</Provider>
);
}
const getRoutes = (user: AppUserState): Array<AppRoute> => [
{
path: `${BASE_PATHNAME}/builder/*`,
element: (
<RequireAuth>
<GetCallCharacters>
{({ data }) => {
const numberOfCharacters =
data?.data?.characters?.filter(
(character) =>
getStatus(character) === CharacterStatusEnum.Active
).length ?? 0;
const maxCharacterSlotsAllowed =
data?.data?.characterSlotLimit ?? Infinity;
const hasMaxCharacters =
numberOfCharacters >= maxCharacterSlotsAllowed;
if (hasMaxCharacters) {
return (
<Navigate to={`${BASE_PATHNAME}/max-characters`} replace />
);
}
return <SheetBuilderApp />;
}}
</GetCallCharacters>
</RequireAuth>
),
},
{
path: `${BASE_PATHNAME}/:characterId/builder/*`,
element: (
<RequireAuth>
<GetCallCharacters>{() => <SheetBuilderApp />}</GetCallCharacters>
</RequireAuth>
),
},
{
path: `${BASE_PATHNAME}/:characterId/:shareId`,
element: user ? (
<GetCallCharacters>{() => <SheetBuilderApp />}</GetCallCharacters>
) : (
<SheetBuilderApp />
),
},
{
path: `${BASE_PATHNAME}/:characterId`,
element: user ? (
<GetCallCharacters>{() => <SheetBuilderApp />}</GetCallCharacters>
) : (
<SheetBuilderApp />
),
},
{
path: `${BASE_PATHNAME}/max-characters`,
element: <MaxCharactersMessage />,
},
{
path: BASE_PATHNAME,
element: (
<RequireAuth>
<GetCallCharacters>
{({ isLoading, data, refetch, error }) => (
<MyCharacters
characterQuery={{ isLoading, data, refetch, error }}
/>
)}
</GetCallCharacters>
</RequireAuth>
),
},
{
path: "/",
element: <Navigate to={BASE_PATHNAME} replace />,
},
];
function CharacterSheetProviders() {
const user = useAuth();
const isUserEmpty = isEmpty(user);
const campaignId = useSelector(rulesEngineSelectors.getCampaign)?.id;
const { characterId } = useParams();
return (
<PddContextProvider
logger={console.log}
errorCallback={() => {}}
nodeEnv={process.env.NODE_ENV}
env={config.environment}
user={isUserEmpty ? undefined : user}
gameId={campaignId ? String(campaignId) : undefined}
>
<GameLogContextProvider
baseUrl={config.gameLogApiEndpoint}
ddbApiUrl={config.ddbApiEndpoint}
diceServiceUrl={config.diceApiEndpoint}
authUrl={authEndpoint}
diceThumbnailsUrl={`${config.diceAssetEndpoint}/images/thumbnails`}
entityId={characterId ?? ""}
diceWorkerContext={DiceWorkerContext}
>
<Outlet />
</GameLogContextProvider>
</PddContextProvider>
);
}
export const Routes = () => {
const user = useAuth();
return (
<Router>
{getRoutes(user).map((props: AppRoute) => (
<Route key={props.path} {...props} />
))}
{/* Routes that need the Redux store + GameLog/Pdd providers */}
<Route element={<CharacterSheetLayout />}>
<Route
path={`${BASE_PATHNAME}/builder/*`}
element={
<RequireAuth>
<GetCallCharacters>
{({ data }) => {
const numberOfCharacters =
data?.data?.characters?.filter(
(character) =>
getStatus(character) === CharacterStatusEnum.Active
).length ?? 0;
const maxCharacterSlotsAllowed =
data?.data?.characterSlotLimit ?? Infinity;
const hasMaxCharacters =
numberOfCharacters >= maxCharacterSlotsAllowed;
if (hasMaxCharacters) {
return (
<Navigate
to={`${BASE_PATHNAME}/max-characters`}
replace
/>
);
}
return <SheetBuilderApp />;
}}
</GetCallCharacters>
</RequireAuth>
}
/>
<Route
path={`${BASE_PATHNAME}/premade/*`}
element={
<RequireAuth>
<GetCallCharacters>{() => <SheetBuilderApp />}</GetCallCharacters>
</RequireAuth>
}
/>
{/* Character routes */}
<Route path={`${BASE_PATHNAME}/:characterId`}>
<Route
path="builder/*"
element={
<RequireAuth>
<GetCallCharacters>
{() => <SheetBuilderApp />}
</GetCallCharacters>
</RequireAuth>
}
/>
<Route
path=":shareId"
element={
user ? (
<GetCallCharacters>
{() => <SheetBuilderApp />}
</GetCallCharacters>
) : (
<SheetBuilderApp />
)
}
/>
<Route
index
element={
user ? (
<GetCallCharacters>
{() => <SheetBuilderApp />}
</GetCallCharacters>
) : (
<SheetBuilderApp />
)
}
/>
</Route>
</Route>
{/* Routes that don't need the store */}
<Route
path={`${BASE_PATHNAME}/max-characters`}
element={<MaxCharactersMessage />}
/>
<Route
path={BASE_PATHNAME}
element={
<RequireAuth>
<GetCallCharacters>
{({ isLoading, data, refetch, error }) => (
<MyCharacters
characterQuery={{ isLoading, data, refetch, error }}
/>
)}
</GetCallCharacters>
</RequireAuth>
}
/>
<Route path="/" element={<Navigate to={BASE_PATHNAME} replace />} />
</Router>
);
};
+27 -24
View File
@@ -1,33 +1,36 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import clsx from "clsx";
import { useLayoutEffect } from "react";
import { MaxCharactersMessageText } from "~/components/MaxCharactersMessageText";
import { DDB_MEDIA_URL } from "../constants";
import styles from "./max-characters-message.module.css";
export const MaxCharactersMessage = () => {
//This mimics the same body classes as CharacterBuilderContainer.tsx
useLayoutEffect(() => {
document
.getElementsByTagName("body")[0]
.classList.add(
"site",
"body-rpgcharacterbuilder",
"site-dndbeyond",
"body-rpgcharacter"
);
return () => {
document
.getElementsByTagName("body")[0]
.classList.remove(
"site",
"body-rpgcharacterbuilder",
"site-dndbeyond",
"body-rpgcharacter"
);
};
}, []);
return (
<Box
sx={{
width: "100%",
height: "calc(100vh - 114px)",
background: `url("${DDB_MEDIA_URL}/attachments/1/614/builder1k.jpg") no-repeat center 0px, url("${DDB_MEDIA_URL}/attachments/0/84/background_texture.png") #f9f9f9 !important`,
}}
justifyContent="center"
display="flex"
>
<Stack
justifyContent="center"
sx={{
padding: "20px",
"& > :not(style)": { padding: "20px" },
maxWidth: "650px",
height: "auto",
}}
>
<MaxCharactersMessageText includeTitle={true} useLinks={true} />
</Stack>
</Box>
<div className={clsx([styles.page])}>
<MaxCharactersMessageText includeTitle={true} useLinks={true} />
</div>
);
};
+6 -7
View File
@@ -6,12 +6,11 @@ import { SheetBuilderApp } from "~/tools/js";
export default function Builder() {
const { characterId } = useParams();
const username = useUserName();
let parsedId: number | null = null;
try {
if (characterId) {
parsedId = parseInt(characterId, 10);
}
} catch (e) {}
return <SheetBuilderApp characterId={parsedId} readOnly={!username} />;
return (
<SheetBuilderApp
characterId={parseInt(String(characterId), 10) || null}
readOnly={!username}
/>
);
}