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,214 @@
import { ComponentType, FC, HTMLAttributes, ReactNode } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
AbilityIcon,
Collapsible,
DarkModeNegativeBonusNegativeSvg,
DarkModePositiveBonusPositiveSvg,
NegativeBonusNegativeSvg,
PositiveBonusPositiveSvg,
} from "@dndbeyond/character-components/es";
import {
AbilityUtils,
characterActions,
Constants,
FormatUtils,
ModifierUtils,
ValueUtils,
} from "@dndbeyond/character-rules-engine/es";
import { HtmlContent } from "~/components/HtmlContent";
import { NumberDisplay } from "~/components/NumberDisplay";
import { RuleKeyEnum } from "~/constants";
import { useCharacterTheme } from "~/contexts/CharacterTheme";
import { useSidebar } from "~/contexts/Sidebar";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import { useRuleData } from "~/hooks/useRuleData";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { Preview } from "~/subApps/sheet/components/Sidebar/components/Preview";
import { Ability, SituationalSavingThrowInfo, StatDataContract } from "~/types";
import EditorBox from "../../../../../../tools/js/Shared/components/EditorBox";
import ValueEditor from "../../../../../../tools/js/Shared/components/ValueEditor";
import { appEnvSelectors } from "../../../../../../tools/js/Shared/selectors";
import { PaneInitFailureContent } from "../../components/PaneInitFailureContent";
import {
PaneComponentEnum,
PaneIdentifiersAbilitySavingThrow,
} from "../../types";
import styles from "./styles.module.css";
/*
AbilitySavingThrowsPane is the specific Sidebar for each Ability Saving Throw on a character - (Strength, Dexterity, Constitution, Wisdom, Intelligence, Charisma) - it displays the details, modifiers, any customizations and overrides, as well as the situational bonuses.
*/
interface Props extends HTMLAttributes<HTMLDivElement> {
identifiers: PaneIdentifiersAbilitySavingThrow | null;
}
export const AbilitySavingThrowsPane: FC<Props> = ({
identifiers,
...props
}) => {
const dispatch = useDispatch();
const {
ruleData,
situationalBonusSavingThrowsLookup,
abilityLookup,
entityValueLookup,
} = useCharacterEngine();
const { ruleDataUtils } = useRuleData();
const { isDarkMode } = useCharacterTheme();
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
const {
pane: { paneHistoryPush },
} = useSidebar();
const abilityDataLookup = ruleDataUtils.getStatsLookup(ruleData);
const ability: Ability | null = identifiers && abilityLookup[identifiers.id];
const abilityData: StatDataContract | null =
identifiers && abilityDataLookup[identifiers.id];
const situationalBonusSavingThrowsInfo: SituationalSavingThrowInfo[] | null =
identifiers && situationalBonusSavingThrowsLookup[identifiers.id];
const rulesText = ruleDataUtils.getRule(RuleKeyEnum.SAVING_THROWS, ruleData);
const handleCustomDataUpdate = (
key: number,
value: any,
source: string
): void => {
if (!abilityData) return;
dispatch(
characterActions.valueSet(
key,
value,
source,
ValueUtils.hack__toString(abilityData.id),
ValueUtils.hack__toString(abilityData.entityTypeId)
)
);
};
const handleParentClick = (): void => {
paneHistoryPush(PaneComponentEnum.SAVING_THROWS);
};
if (!ability || !abilityData) {
return <PaneInitFailureContent />;
}
const statId = AbilityUtils.getId(ability);
let sidebarPreviewNode: ReactNode = (
<Preview>
<AbilityIcon
className={styles.icon}
statId={statId}
themeMode={isDarkMode ? "light" : "dark"}
/>
</Preview>
);
return (
<div key={statId} {...props}>
<Header
preview={sidebarPreviewNode}
parent="Saving Throws"
onClick={handleParentClick}
>
{abilityData.name} Saving Throw
<span className={styles.modifier}>
<NumberDisplay type="signed" number={AbilityUtils.getSave(ability)} />
</span>
</Header>
{!isReadonly && (
<Collapsible
layoutType={"minimal"}
header="Customize"
className={styles.customize}
>
<EditorBox className={styles.editorBox}>
<ValueEditor
dataLookup={ValueUtils.getEntityData(
entityValueLookup,
ValueUtils.hack__toString(abilityData.id),
ValueUtils.hack__toString(abilityData.entityTypeId)
)}
onDataUpdate={handleCustomDataUpdate}
valueEditors={[
Constants.AdjustmentTypeEnum.SAVING_THROW_OVERRIDE,
Constants.AdjustmentTypeEnum.SAVING_THROW_MAGIC_BONUS,
Constants.AdjustmentTypeEnum.SAVING_THROW_MISC_BONUS,
Constants.AdjustmentTypeEnum.SAVING_THROW_PROFICIENCY_LEVEL,
]}
ruleData={ruleData}
/>
</EditorBox>
</Collapsible>
)}
{situationalBonusSavingThrowsInfo && (
<div className={styles.situational}>
{situationalBonusSavingThrowsInfo.map((situationalBonus) => {
let abilityNode: ReactNode;
if (
situationalBonus.type ===
Constants.SituationalBonusSavingThrowTypeEnum.MODIFIER
) {
let restriction = ModifierUtils.getRestriction(
situationalBonus.extra
);
if (restriction) {
restriction = restriction.trim();
restriction = FormatUtils.lowerCaseLetters(restriction, 0);
abilityNode = <>{restriction}</>;
}
}
if (!abilityNode) {
abilityNode = <>on saves</>;
}
const saveAmount = situationalBonus.value;
let IconComponent: ComponentType<any>;
if (situationalBonus.value >= 0) {
IconComponent = isDarkMode
? DarkModePositiveBonusPositiveSvg
: PositiveBonusPositiveSvg;
} else {
IconComponent = isDarkMode
? DarkModeNegativeBonusNegativeSvg
: NegativeBonusNegativeSvg;
}
return (
<div
className={styles.situationalBonus}
key={situationalBonus.key}
>
<IconComponent className={styles.situationalBonusIcon} />
<span className={styles.situationalBonusValue}>
{saveAmount}
</span>
<span className={styles.situationalBonusRestriction}>
{abilityNode}
</span>
</div>
);
})}
</div>
)}
{rulesText && rulesText.description && (
<HtmlContent
className={styles.description}
html={rulesText.description}
withoutTooltips
/>
)}
</div>
);
};