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:
+214
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user