Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,105 @@
import { FC, useContext, useEffect, useState } from "react";
import { FeatureManager } from "@dndbeyond/character-rules-engine";
import { ThemeButton } from "~/tools/js/Shared/components/common/Button";
import DataLoadingStatusEnum from "~/tools/js/Shared/constants/DataLoadingStatusEnum";
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
import { AppLoggerUtils, AppNotificationUtils } from "~/tools/js/Shared/utils";
import Collapsible, {
CollapsibleHeaderContent,
} from "~/tools/js/smartComponents/Collapsible";
export const BlessingShoppe: FC<{}> = () => {
const { characterFeaturesManager } = useContext(
CharacterFeaturesManagerContext
);
const [loadingStatus, setLoadingStatus] = useState<DataLoadingStatusEnum>(
DataLoadingStatusEnum.NOT_INITIALIZED
);
const [blessingShoppe, setBlessingShoppe] = useState<Array<FeatureManager>>(
[]
);
const [blessings, setBlessings] = useState<Array<FeatureManager>>([]);
async function fetchBlessings() {
const blessings = await characterFeaturesManager.getBlessings();
setBlessings(blessings);
}
useEffect(() => {
if (loadingStatus !== DataLoadingStatusEnum.LOADED) {
characterFeaturesManager
.getBlessingShoppe()
.then((blessingShoppe) => {
setLoadingStatus(DataLoadingStatusEnum.LOADED);
setBlessingShoppe(blessingShoppe);
})
.catch(AppLoggerUtils.handleAdhocApiError);
}
fetchBlessings();
}, [loadingStatus, characterFeaturesManager]);
return (
<div>
<p>
A blessing is usually bestowed by a god or a godlike being. A character
might receive a blessing from a deity for doing something truly
momentous an accomplishment that catches the attention of both gods
and mortals.
</p>
<div>
{blessingShoppe.map((blessing) => {
return (
<Collapsible
key={blessing.getId()}
layoutType={"minimal"}
header={
<CollapsibleHeaderContent
heading={blessing.getName()}
callout={
characterFeaturesManager.hasBlessing(blessing) ? (
<ThemeButton
onClick={() => {
blessing.handleRemove(() => {
fetchBlessings();
// AppNotificationUtils.dispatchSuccess(
// 'Removed Blessing',
// 'You removed a blessing',
// );
});
}}
size="small"
>
Remove
</ThemeButton>
) : (
<ThemeButton
onClick={() => {
blessing.handleAdd(() => {
fetchBlessings();
AppNotificationUtils.dispatchSuccess(
"Added Blessing",
"You added a blessing"
);
});
}}
size="small"
style="outline"
>
Add
</ThemeButton>
)
}
/>
}
>
{blessing.getDescription()}
</Collapsible>
);
})}
</div>
</div>
);
};
@@ -0,0 +1,130 @@
import clsx from "clsx";
import { FC, ReactNode } from "react";
import { FeatManager } from "@dndbeyond/character-rules-engine/es";
import { Reference } from "~/components/Reference";
import { DataOriginTypeEnum } from "~/constants";
import { useCharacterTheme } from "~/contexts/CharacterTheme";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import Collapsible, {
CollapsibleHeaderContent,
} from "~/tools/js/smartComponents/Collapsible";
import { TodoIcon } from "~/tools/js/smartComponents/Icons";
import PrerequisiteFailureSummary from "~/tools/js/smartComponents/PrerequisiteFailureSummary";
import { CharacterTheme } from "~/types";
import {
RemoveButton,
ThemeButton,
} from "../../../../../../../tools/js/Shared/components/common/Button";
import { FeatDetail } from "../FeatDetail";
import styles from "./styles.module.css";
interface Props {
feat: FeatManager;
enableRemove?: boolean;
enableAdd?: boolean;
showFailures?: boolean;
theme: CharacterTheme;
}
export const Feat: FC<Props> = ({
feat,
enableAdd = true,
enableRemove = true,
showFailures = false,
theme,
}) => {
const { entityUtils } = useCharacterEngine();
const { isDarkMode } = useCharacterTheme();
let missingChoiceCount: number = feat.getUnfinishedChoices().length;
let calloutNode: ReactNode = null;
let dataOrigin = feat.getDataOrigin();
let dataOriginType = feat.getDataOriginType();
switch (dataOriginType) {
case DataOriginTypeEnum.ADHOC:
if (enableRemove) {
calloutNode = (
<RemoveButton
onClick={feat.handleRemove}
style="filled"
className={styles.button}
/>
);
}
break;
case DataOriginTypeEnum.SIMULATED:
if (enableAdd) {
calloutNode = (
<ThemeButton
onClick={feat.handleAdd}
size="small"
style="outline"
className={styles.button}
>
Add
</ThemeButton>
);
}
break;
default:
const dataOriginExtra = entityUtils.getDataOriginName(
dataOrigin,
"Unknown",
true
);
calloutNode = (
<span className={styles.origin}>
<span className={styles.originLabel}>From</span>
<span className={styles.originName}>{dataOriginExtra}</span>
</span>
);
}
return (
<Collapsible
layoutType={"minimal"}
className={styles.feat}
header={
<CollapsibleHeaderContent
heading={
<div>
<div>
<span>{feat.getName()}</span>
{missingChoiceCount > 0 && (
<TodoIcon
theme={theme}
title={`Choice${
missingChoiceCount !== 1 ? "s" : ""
} Needed`}
/>
)}
</div>
<span className={styles.source}>
<Reference name={feat.getPrimarySourceName()} />
</span>
{showFailures && (
<PrerequisiteFailureSummary
failures={feat.getPrerequisiteFailures()}
className={styles.failures}
/>
)}
</div>
}
callout={calloutNode}
/>
}
>
<FeatDetail
featManager={feat}
className={clsx([styles.detail, isDarkMode && styles.dark])}
/>
</Collapsible>
);
};
@@ -0,0 +1,35 @@
import { FC, HTMLAttributes } from "react";
import { FeatManager } from "@dndbeyond/character-rules-engine/es";
import { HtmlContent } from "~/components/HtmlContent";
import { DetailChoiceFeat } from "~/tools/js/Shared/containers/DetailChoice";
import styles from "./styles.module.css";
interface Props extends HTMLAttributes<HTMLDivElement> {
featManager: FeatManager;
}
export const FeatDetail: FC<Props> = ({ featManager, className, ...props }) => {
const prerequisiteDescription = featManager.getPrerequisiteDescription();
const featDescription = featManager.getDescription();
return (
<div className={className} {...props}>
{prerequisiteDescription && (
<div className={styles.prereq}>
Prerequisite: {prerequisiteDescription}
</div>
)}
{featDescription && (
<HtmlContent html={featDescription} withoutTooltips />
)}
{featManager.getChoices().length > 0 && (
<div className={styles.choices}>
<DetailChoiceFeat featId={featManager.getId()} />
</div>
)}
</div>
);
};
@@ -0,0 +1,131 @@
import { FC, useContext, useEffect, useState } from "react";
import { FeatManager } from "@dndbeyond/character-rules-engine";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import { useSource } from "~/hooks/useSource";
import DataLoadingStatusEnum from "~/tools/js/Shared/constants/DataLoadingStatusEnum";
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
import { AppLoggerUtils } from "~/tools/js/Shared/utils";
import { isNotNullOrUndefined } from "~/tools/js/Shared/utils/TypeScript";
import Collapsible, {
CollapsibleHeaderContent,
CollapsibleHeading,
} from "~/tools/js/smartComponents/Collapsible";
import LoadingPlaceholder from "~/tools/js/smartComponents/LoadingPlaceholder";
import { Feat } from "../Feat/Feat";
import styles from "../styles.module.css";
export const FeatShoppe: FC<{}> = () => {
const { characterFeaturesManager } = useContext(
CharacterFeaturesManagerContext
);
const {
feats: currentCharacterFeats,
characterTheme: theme,
preferences,
} = useCharacterEngine();
const { getSourceCategoryGroups } = useSource();
const [loadingStatus, setLoadingStatus] = useState<DataLoadingStatusEnum>(
DataLoadingStatusEnum.NOT_INITIALIZED
);
const [featShoppe, setFeatShoppe] = useState<Array<FeatManager>>([]);
useEffect(() => {
if (loadingStatus !== DataLoadingStatusEnum.LOADED) {
characterFeaturesManager
.getFeatShoppe()
.then((featShoppe) => {
setLoadingStatus(DataLoadingStatusEnum.LOADED);
setFeatShoppe(featShoppe);
})
.catch(AppLoggerUtils.handleAdhocApiError);
} else {
setFeatShoppe(characterFeaturesManager.updateFeatShoppe(featShoppe));
}
}, [currentCharacterFeats]);
const getFeatsInSourceCategoryGroups = (feats: FeatManager[]) => {
const featDefs = feats
.map((feat) => feat.getDefinition())
.filter(isNotNullOrUndefined);
return getSourceCategoryGroups(featDefs).map((group) => {
return {
...group,
items: characterFeaturesManager.transformLoadedFeats(group.items),
};
});
};
return loadingStatus !== DataLoadingStatusEnum.LOADED ? (
<LoadingPlaceholder />
) : (
<>
<div className={styles.featList}>
{getFeatsInSourceCategoryGroups(
characterFeaturesManager.getAvailableFeats(featShoppe)
).map((group) => (
<Collapsible
key={group.id + "available"}
layoutType="minimal"
initiallyCollapsed={false}
header={<h2>{group.name}</h2>}
>
<div>
{group.items.map((feat) => (
<Feat theme={theme} key={feat.getId()} feat={feat} />
))}
</div>
</Collapsible>
))}
</div>
{characterFeaturesManager.getUnavailableFeats(featShoppe).length > 0 && (
<Collapsible
header={
<CollapsibleHeaderContent
heading={
<CollapsibleHeading>
Unavailable Feats
<span className={styles.unavailableCallout}>
Prerequisites Not Met
</span>
</CollapsibleHeading>
}
/>
}
className={styles.featList}
>
<>
{getFeatsInSourceCategoryGroups(
characterFeaturesManager.getUnavailableFeats(featShoppe)
).map((group) => (
<Collapsible
key={group.id + "unavailable"}
layoutType="minimal"
initiallyCollapsed={false}
header={<h2>{group.name}</h2>}
>
<div>
{group.items.map((feat) => (
<Feat
theme={theme}
key={feat.getId()}
feat={feat}
enableAdd={!preferences?.enforceFeatRules}
showFailures={true}
/>
))}
</div>
</Collapsible>
))}
</>
</Collapsible>
)}
</>
);
};
@@ -0,0 +1,66 @@
import { FC } from "react";
import { useSelector } from "react-redux";
import { useFeatureFlags } from "~/contexts/FeatureFlag";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import SettingsButton from "~/tools/js/CharacterSheet/components/SettingsButton";
import { SettingsContextsEnum } from "~/tools/js/Shared/containers/panes/SettingsPane/typings";
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
import Collapsible, {
CollapsibleHeaderContent,
CollapsibleHeading,
} from "~/tools/js/smartComponents/Collapsible";
import { BlessingShoppe } from "./BlessingShoppe";
import { FeatShoppe } from "./FeatShoppe";
import styles from "./styles.module.css";
export const FeatsManagePane: FC<{}> = () => {
const { gfsBlessingsUiFlag } = useFeatureFlags();
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
if (isReadonly) {
return null;
}
return (
<div>
<Header
callout={
<SettingsButton
context={SettingsContextsEnum.FEATURES}
isReadonly={isReadonly}
/>
}
>
Manage {gfsBlessingsUiFlag ? "Features" : "Feats"}
</Header>
{gfsBlessingsUiFlag ? (
<Collapsible
header={
<CollapsibleHeaderContent
heading={<CollapsibleHeading>Add Feats</CollapsibleHeading>}
/>
}
className={styles.featList}
>
<FeatShoppe />
</Collapsible>
) : (
<FeatShoppe />
)}
{gfsBlessingsUiFlag && (
<Collapsible
header={
<CollapsibleHeaderContent
heading={<CollapsibleHeading>Add Blessings</CollapsibleHeading>}
/>
}
className={styles.featList}
>
<BlessingShoppe />
</Collapsible>
)}
</div>
);
};