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
+100 -98
View File
@@ -1,11 +1,12 @@
// import axe from '@axe-core/react';
import { css } from "@mui/material";
import GlobalStyles from "@mui/material/GlobalStyles";
import React from "react";
import React, { useEffect } from "react";
import Modal from "react-modal";
import { Provider } from "react-redux";
import { useLocation, useMatch, useNavigate } from "react-router-dom";
import { Store } from "redux";
import { useStore } from "react-redux";
import {
useLocation,
useMatch,
useNavigate,
useSearchParams,
} from "react-router-dom";
import {
characterEnvActions,
@@ -15,16 +16,15 @@ import {
} from "@dndbeyond/character-rules-engine/es";
import config from "~/config";
import { useAuth } from "~/contexts/Authentication";
import { CharacterThemeProvider } from "~/contexts/CharacterTheme";
import { SidebarProvider } from "~/contexts/Sidebar";
import useUserId from "~/hooks/useUserId";
import useUserName from "~/hooks/useUserName";
import useUserRoles from "~/hooks/useUserRoles";
import "../styles/styles.scss";
import CharacterBuilderContainer from "./CharacterBuilder/containers/CharacterBuilderContainer";
import CharacterSheetContainer from "./CharacterSheet/containers/CharacterSheetContainer";
import configureSheetStore from "./CharacterSheet/store/configureStore";
import { appEnvActions } from "./Shared/actions";
import { Managers } from "./Shared/managers";
import {
@@ -52,9 +52,10 @@ export const SheetBuilderApp: React.FC<Props> = ({
redirect,
}) => {
const username = useUserName();
const userId = useUserId();
const user = useAuth();
const userRoles = useUserRoles();
const navigate = useNavigate();
const matchPremadePage = useMatch(`${BASE_PATHNAME}/premade/*`);
const matchBuilderWithOutCharacter = useMatch(`${BASE_PATHNAME}/builder/*`);
const matchBuilderWithCharacter = useMatch(
`${BASE_PATHNAME}/:characterId/builder/*`
@@ -64,6 +65,8 @@ export const SheetBuilderApp: React.FC<Props> = ({
`${BASE_PATHNAME}/:characterId/:shareId`
);
const currentPath = useLocation().pathname;
const [searchParams] = useSearchParams();
const isVttView = searchParams.get("view") === "vtt";
if (config === undefined) {
throw Error("Missing Config");
@@ -71,7 +74,11 @@ export const SheetBuilderApp: React.FC<Props> = ({
let appContext: Constants.AppContextTypeEnum | null = null;
switch (true) {
case !!(matchBuilderWithOutCharacter || matchBuilderWithCharacter):
case !!(
matchBuilderWithOutCharacter ||
matchBuilderWithCharacter ||
matchPremadePage
):
appContext = Constants.AppContextTypeEnum.BUILDER;
break;
case !!(matchSheet || matchSheetWithShareId):
@@ -93,11 +100,8 @@ export const SheetBuilderApp: React.FC<Props> = ({
debug: appConfig.debug,
});
let store: Store = configureSheetStore();
store.dispatch({
type: "SET_ROUTE_CONTROLS",
payload: { navigate, currentPath },
});
const store = useStore();
const initializedRef = React.useRef(false);
let parsedDiceEnabled: boolean = true;
if (!diceFeatureConfiguration.enabled) {
@@ -139,25 +143,54 @@ export const SheetBuilderApp: React.FC<Props> = ({
canUseCurrencyContainers: true,
});
store.dispatch(
characterEnvActions.dataSet({
context: appContext,
})
);
// CharacterSheet.componentDidMount fires before parent useEffects, so characterId and
// appContext must be in the store synchronously before children mount. Without this,
// the character load saga reads a null characterId and the character fails to load.
// The useEffect below handles re-dispatching once username/user resolve asynchronously.
if (!initializedRef.current) {
initializedRef.current = true;
store.dispatch(
characterEnvActions.dataSet({
context: appContext,
})
);
store.dispatch(
appEnvActions.dataSet({
username,
userId: user?.id ? Number(user.id) : -1,
userRoles,
characterId,
authEndpoint: config.authEndpoint,
redirect,
diceEnabled: parsedDiceEnabled,
diceFeatureConfiguration: diceFeatureConfiguration,
gameLog: gameLogSettings,
})
);
}
store.dispatch(
appEnvActions.dataSet({
username,
userId: userId ? Number(userId) : -1,
userRoles,
characterId,
authEndpoint: config.authEndpoint,
redirect,
diceEnabled: parsedDiceEnabled,
diceFeatureConfiguration: diceFeatureConfiguration,
gameLog: gameLogSettings,
})
);
useEffect(() => {
store.dispatch({
type: "SET_ROUTE_CONTROLS",
payload: { navigate, currentPath },
});
}, [navigate, currentPath]);
useEffect(() => {
store.dispatch(
appEnvActions.dataSet({
username,
userId: user?.id ? Number(user.id) : -1,
userRoles,
characterId,
authEndpoint: config.authEndpoint,
redirect,
diceEnabled: parsedDiceEnabled,
diceFeatureConfiguration: diceFeatureConfiguration,
gameLog: gameLogSettings,
})
);
}, [username, user]);
// Setup axe-core in development, not in production
if (process.env.NODE_ENV !== "production") {
@@ -165,70 +198,39 @@ export const SheetBuilderApp: React.FC<Props> = ({
}
Modal.setAppElement("#character-tools-target");
// If the view is the VTT view, set --top-navigation-height to 0 because the top nav will not be present.
useEffect(() => {
if (isVttView) {
document.body.style.setProperty("--top-navigation-height", "0px");
}
}, []);
return (
<Provider store={store}>
<Managers>
<CharacterThemeProvider>
<SidebarProvider>
<GlobalStyles
styles={() => {
return css`
input,
button,
select,
textarea {
font-family: Roboto, Helvetica, sans-serif;
font-size: 100%;
margin: 0;
vertical-align: baseline;
}
input[type="text"],
input[type="number"],
input[type="search"],
input[type="password"],
input[type="email"],
textarea,
select {
background-color: #fff;
background: #fff none;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
color: #000;
padding: 4px;
transition: 0.2s linear border, 0.2s linear box-shadow;
}
input[type="text"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
textarea:focus {
border-color: #242527;
box-shadow: 0 0 4px rgba(36, 37, 39, 0.6);
background-color: #f9f9f9;
outline: 0;
}
button,
input[type="button"],
input[type="reset"],
input[type="submit"] {
cursor: pointer;
}
`;
}}
/>
{matchBuilderWithOutCharacter || matchBuilderWithCharacter ? (
<CharacterBuilderContainer />
) : (
<CharacterSheetContainer authEndpoint={config.authEndpoint} />
)}
</SidebarProvider>
</CharacterThemeProvider>
</Managers>
</Provider>
<Managers>
<CharacterThemeProvider>
<Content />
</CharacterThemeProvider>
</Managers>
);
};
const Content = () => {
const matchBuilderWithOutCharacter = useMatch(`${BASE_PATHNAME}/builder/*`);
const matchBuilderWithCharacter = useMatch(
`${BASE_PATHNAME}/:characterId/builder/*`
);
const matchPremadePage = useMatch(`${BASE_PATHNAME}/premade/*`);
return (
<SidebarProvider>
{matchBuilderWithOutCharacter ||
matchBuilderWithCharacter ||
matchPremadePage ? (
<CharacterBuilderContainer isPremadeListing={!!matchPremadePage} />
) : (
<CharacterSheetContainer authEndpoint={config.authEndpoint} />
)}
</SidebarProvider>
);
};