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,562 @@
import React from "react";
import { connect, DispatchProp } from "react-redux";
import { PrivacyTypeRadio } from "@dndbeyond/character-components/es";
import {
AnySimpleDataType,
characterActions,
CharacterPreferences,
CharacterTheme,
CharacterUtils,
CharClass,
ClassSpellListSpellsLookup,
ClassUtils,
Constants,
DataOriginRefData,
HelperUtils,
HtmlSelectOption,
Race,
RuleData,
RuleDataUtils,
rulesEngineSelectors,
RaceUtils,
} from "@dndbeyond/character-rules-engine/es";
import { Dice } from "@dndbeyond/dice";
import { SourceCategoryDescription } from "~/constants";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import config from "../../../../config";
import { appEnvActions } from "../../../actions";
import {
CheckboxInfo,
FormCheckBoxesField,
} from "../../../components/common/FormCheckBoxesField";
import { appEnvSelectors } from "../../../selectors";
import { SharedAppState } from "../../../stores/typings";
import { PaneIdentifierUtils } from "../../../utils";
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
import { ToggleInfo } from "./PreferencesPaneTogglesField";
interface Props extends DispatchProp {
activeSourceCategories: Array<number>;
preferences: CharacterPreferences;
ruleData: RuleData;
diceEnabled: boolean;
classes: Array<CharClass>;
species: Race | null;
dataOriginRefData: DataOriginRefData;
classSpellListSpellsLookup: ClassSpellListSpellsLookup;
characterTheme: CharacterTheme;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class PreferencesPane extends React.PureComponent<Props> {
static defaultProps = {
diceEnabled: false,
};
handlePreferenceChange = (
prefKey: string,
value: AnySimpleDataType
): void => {
const { dispatch } = this.props;
const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
if (typedPrefKey !== null) {
dispatch(characterActions.preferenceChoose(typedPrefKey, value));
}
};
handleIntPreferenceChange = (prefKey: string, value: number | null): void => {
const { dispatch } = this.props;
const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
if (typedPrefKey !== null) {
dispatch(characterActions.preferenceChoose(typedPrefKey, value));
}
};
handleSourceCategoryChange = (sourceId: number, isActive: boolean): void => {
const { dispatch, activeSourceCategories } = this.props;
let newSourceCats: Array<number> = [];
if (isActive) {
newSourceCats = [...activeSourceCategories, sourceId];
} else {
newSourceCats = activeSourceCategories.filter((id) => id !== sourceId);
}
dispatch(characterActions.activeSourceCategoriesSet(newSourceCats));
};
handleOptionalClassFeaturesPreferenceChangePromise = (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
): void => {
const { paneHistoryStart, classes, classSpellListSpellsLookup } =
this.props;
const spellListIds: Array<number> =
ClassUtils.getUpdateEnableOptionalClassFeaturesSpellListIdsToRemove(
classes,
newIsEnabled
);
const hasSpellsToRemove = spellListIds.some((id) =>
classSpellListSpellsLookup.hasOwnProperty(id)
);
if (!hasSpellsToRemove) {
this.handlePreferenceChange("enableOptionalClassFeatures", newIsEnabled);
accept();
} else {
paneHistoryStart(
PaneComponentEnum.PREFERENCES_OPTIONAL_CLASS_FEATURES_CONFIRM,
PaneIdentifierUtils.generatePreferenceOptionalClassFeaturesConfirm(
spellListIds,
newIsEnabled
)
);
reject();
}
};
handleOptionalOriginsPreferenceChangePromise = (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
): void => {
const { paneHistoryStart, species, classSpellListSpellsLookup } =
this.props;
if (!species) {
this.handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
accept();
return;
}
const spellListIds: Array<number> =
RaceUtils.getUpdateEnableOptionalOriginsSpellListIdsToRemove(
species,
newIsEnabled
);
const hasSpellsToRemove = spellListIds.some((id) =>
classSpellListSpellsLookup.hasOwnProperty(id)
);
if (!hasSpellsToRemove) {
this.handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
accept();
} else {
paneHistoryStart(
PaneComponentEnum.PREFERENCES_OPTIONAL_ORIGINS_CONFIRM,
PaneIdentifierUtils.generatePreferenceOptionalOriginsConfirm(
spellListIds,
newIsEnabled
)
);
reject();
}
};
handleProgressionPreferenceChangePromise = (
newId: string,
oldId: string,
accept: () => void,
reject: () => void
): void => {
const { paneHistoryStart } = this.props;
const newIdValue = HelperUtils.parseInputInt(newId);
if (newIdValue !== null) {
paneHistoryStart(
PaneComponentEnum.PREFERENCES_PROGRESSION_CONFIRM,
PaneIdentifierUtils.generatePreferenceProgressionConfirm(newIdValue)
);
}
reject();
};
handleHitPointPreferenceChangePromise = (
newId: string,
oldId: string,
accept: () => void,
reject: () => void
): void => {
const { paneHistoryStart } = this.props;
const newIdValue = HelperUtils.parseInputInt(newId);
if (newIdValue !== null) {
paneHistoryStart(
PaneComponentEnum.PREFERENCES_HIT_POINT_CONFIRM,
PaneIdentifierUtils.generatePreferenceHitPointConfirm(newIdValue)
);
}
reject();
};
handleDiceToggle = (): void => {
const { dispatch, diceEnabled } = this.props;
const newDiceEnabledSetting: boolean = !diceEnabled;
try {
localStorage.setItem("dice-enabled", newDiceEnabledSetting.toString());
Dice.setEnabled(newDiceEnabledSetting);
} catch (e) {}
dispatch(
appEnvActions.dataSet({
diceEnabled: newDiceEnabledSetting,
})
);
};
handleChangePrivacy = (value: number | null): void => {
const typedPrefKey = CharacterUtils.getPreferenceKey("privacyType");
if (typedPrefKey !== null) {
this.props.dispatch(
characterActions.preferenceChoose(typedPrefKey, value)
);
}
};
render() {
const { preferences, activeSourceCategories, ruleData, diceEnabled } =
this.props;
const {
useHomebrewContent,
encumbranceType,
hitPointType,
progressionType,
abilityScoreDisplayType,
sharingType,
privacyType,
ignoreCoinWeight,
enforceFeatRules,
enforceMulticlassRules,
showScaledSpells,
enableOptionalClassFeatures,
enableOptionalOrigins,
enableDarkMode,
} = preferences;
const sourceCategories = RuleDataUtils.getSourceCategories(ruleData);
const encumbranceOptions: Array<HtmlSelectOption> = [
{
label: "Use Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.ENCUMBRANCE,
},
{
label: "No Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.NONE,
},
{
label: "Variant Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.VARIANT,
},
];
const hpOptions: Array<HtmlSelectOption> = [
{ label: "Fixed", value: Constants.PreferenceHitPointTypeEnum.FIXED },
{ label: "Manual", value: Constants.PreferenceHitPointTypeEnum.MANUAL },
];
const advancementOptions: Array<HtmlSelectOption> = [
{
label: "Milestone",
value: Constants.PreferenceProgressionTypeEnum.MILESTONE,
},
{ label: "XP", value: Constants.PreferenceProgressionTypeEnum.XP },
];
const abilityDisplayOptions: Array<HtmlSelectOption> = [
{
label: "Modifiers Top",
value: Constants.PreferenceAbilityScoreDisplayTypeEnum.MODIFIERS_TOP,
},
{
label: "Scores Top",
value: Constants.PreferenceAbilityScoreDisplayTypeEnum.SCORES_TOP,
},
];
const sharingOptions: Array<HtmlSelectOption> = [
{ label: "Full", value: Constants.PreferenceSharingTypeEnum.FULL },
{ label: "Limited", value: Constants.PreferenceSharingTypeEnum.LIMITED },
// {label: 'Stat Block', value: Constants.PreferenceSharingTypeEnum.STAT_BLOCK},
];
let sourceToggles: Array<CheckboxInfo> = [];
let partneredSourceToggles: Array<CheckboxInfo> = [];
sourceCategories.forEach((sourceCategory) => {
if (!sourceCategory.isToggleable) {
return null;
}
const toggle: ToggleInfo = {
label: `${sourceCategory.name}`,
initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
onChange: this.handleSourceCategoryChange.bind(this, sourceCategory.id),
sortOrder: sourceCategory.sortOrder,
};
if (sourceCategory.isPartneredContent) {
partneredSourceToggles.push(toggle);
} else {
sourceToggles.push(toggle);
}
});
return (
<div className="ct-preferences-pane">
<Header>Preferences</Header>
<FormCheckBoxesField
heading="Sources"
description={SourceCategoryDescription.official}
checkboxes={[
{
label: "Homebrew",
initiallyEnabled: useHomebrewContent,
onChange: this.handlePreferenceChange.bind(
this,
"useHomebrewContent"
),
sortOrder: 0,
},
...sourceToggles,
]}
themed={true}
showAccordion={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<FormCheckBoxesField
heading="Partnered Content"
description={SourceCategoryDescription.partnered}
checkboxes={partneredSourceToggles}
checkUncheckAllEnabled={true}
themed={true}
accordionHeading="Categories"
variant="sidebar"
darkMode={enableDarkMode}
showAccordion={true}
/>
<FormCheckBoxesField
heading="Underdark Mode"
description="Enables dark mode for this character"
checkboxes={[
{
initiallyEnabled: enableDarkMode,
label: "Enable dark mode",
onChange: this.handlePreferenceChange.bind(
this,
"enableDarkMode"
),
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<FormCheckBoxesField
heading="Dice Rolling"
description="Enables digital dice rolling for this character"
checkboxes={[
{
initiallyEnabled: diceEnabled,
label: "Enable Dice Rolling",
onChange: this.handleDiceToggle,
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<FormCheckBoxesField
heading="Optional Features"
description="Allow or restrict optional features for this character."
checkboxes={[
{
label: "Optional Class Features",
initiallyEnabled: enableOptionalClassFeatures,
onChangePromise:
this.handleOptionalClassFeaturesPreferenceChangePromise,
},
{
label: "Customize Your Origin",
initiallyEnabled: enableOptionalOrigins,
onChangePromise:
this.handleOptionalOriginsPreferenceChangePromise,
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<PreferencesPaneSelectField
heading="Advancement Type"
description="Story-based character progression / XP-based character progression"
onChangePromise={this.handleProgressionPreferenceChangePromise}
initialOptionRemoved={true}
options={advancementOptions}
initialValue={progressionType}
block={true}
/>
<PreferencesPaneSelectField
heading="Hit Point Type"
description="When leveling up, increase hit points by the fixed value for your chosen class or manually enter a rolled value"
onChangePromise={this.handleHitPointPreferenceChangePromise}
initialOptionRemoved={true}
options={hpOptions}
initialValue={hitPointType}
block={true}
/>
<FormCheckBoxesField
heading="Use Prerequisites"
description="Allow or restrict choices based on rule prerequisites for the following for this character"
checkboxes={[
{
label: "Feats",
initiallyEnabled: enforceFeatRules,
onChange: this.handlePreferenceChange.bind(
this,
"enforceFeatRules"
),
},
{
label: "Multiclass Requirements",
initiallyEnabled: enforceMulticlassRules,
onChange: this.handlePreferenceChange.bind(
this,
"enforceMulticlassRules"
),
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<FormCheckBoxesField
heading="Show level-scaled spells"
description="Display and highlight available spells to cast with higher level spell slots"
checkboxes={[
{
initiallyEnabled: showScaledSpells,
label: "Show Level-Scaled Spells",
onChange: this.handlePreferenceChange.bind(
this,
"showScaledSpells"
),
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<PreferencesPaneSelectField
heading="Encumbrance Type"
description="Use the standard encumbrance rules / Disable the encumbrance display / Use the more detailed rules for encumbrance"
onChange={this.handleIntPreferenceChange.bind(
this,
"encumbranceType"
)}
initialOptionRemoved={true}
options={encumbranceOptions}
initialValue={encumbranceType}
block={true}
/>
<FormCheckBoxesField
heading="Ignore Coin Weight"
description="Coins do not count against your total weight carried (50 coins weigh 1 lb.)"
checkboxes={[
{
initiallyEnabled: ignoreCoinWeight,
label: "Ignore Coin Weight",
onChange: this.handlePreferenceChange.bind(
this,
"ignoreCoinWeight"
),
},
]}
themed={true}
variant="sidebar"
darkMode={enableDarkMode}
/>
<PreferencesPaneSelectField
heading="Ability Score/Modifier Display"
description="Reverse the arrangement of ability modifiers and scores"
onChange={this.handleIntPreferenceChange.bind(
this,
"abilityScoreDisplayType"
)}
initialOptionRemoved={true}
options={abilityDisplayOptions}
initialValue={abilityScoreDisplayType}
block={true}
/>
<PrivacyTypeRadio
handleChange={(e) =>
this.handleChangePrivacy(parseInt(e?.target?.value))
}
darkMode={enableDarkMode}
initialValue={privacyType}
themed={true}
variant="sidebar"
/>
<div className="ct-preferences-pane__version">
<div className="ct-preferences-pane__version-label">Version:</div>
<div className="ct-preferences-pane__version-value">
{config.version}
</div>
</div>
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
ruleData: rulesEngineSelectors.getRuleData(state),
preferences: rulesEngineSelectors.getCharacterPreferences(state),
activeSourceCategories:
rulesEngineSelectors.getActiveSourceCategories(state),
diceEnabled: appEnvSelectors.getDiceEnabled(state),
classes: rulesEngineSelectors.getClasses(state),
species: rulesEngineSelectors.getRace(state),
classSpellListSpellsLookup:
rulesEngineSelectors.getClassSpellListSpellsLookup(state),
dataOriginRefData: rulesEngineSelectors.getDataOriginRefData(state),
characterTheme: rulesEngineSelectors.getCharacterTheme(state),
};
}
const PreferencesPaneContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <PreferencesPane paneHistoryStart={paneHistoryStart} {...props} />;
};
export default connect(mapStateToProps)(PreferencesPaneContainer);
@@ -0,0 +1,155 @@
import { uniqueId } from "lodash";
import React from "react";
import { Select } from "@dndbeyond/character-components/es";
import {
HelperUtils,
HtmlSelectOption,
} from "@dndbeyond/character-rules-engine/es";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
interface Props {
initialValue: number | null;
onChange?: (value: number | null) => void;
onChangePromise?: (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
) => void;
heading: React.ReactNode;
className: string;
initialOptionRemoved: boolean;
options: Array<HtmlSelectOption>;
description: string;
block: boolean;
placeholder?: string;
}
interface State {
value: number | null;
}
export default class PreferencesPaneSelectField extends React.PureComponent<
Props,
State
> {
static defaultProps = {
initialValue: null,
className: "",
initialOptionRemoved: false,
block: false,
};
constructor(props: Props) {
super(props);
this.state = {
value: props.initialValue,
};
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>,
snapshot?: any
): void {
const { initialValue } = this.props;
if (initialValue !== prevProps.initialValue) {
this.setState({
value: initialValue,
});
}
}
handleChange = (value: string): void => {
const { onChange } = this.props;
let parseValue = HelperUtils.parseInputInt(value);
if (onChange) {
onChange(parseValue);
}
this.setState({
value: parseValue,
});
};
handleChangePromise = (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
): void => {
const { onChangePromise } = this.props;
if (onChangePromise) {
onChangePromise(
newValue,
oldValue,
() => {
let parseValue = HelperUtils.parseInputInt(newValue);
this.setState({
value: parseValue,
});
accept();
},
reject
);
}
};
render() {
const { value } = this.state;
const {
onChangePromise,
onChange,
block,
heading,
options,
className,
initialOptionRemoved,
description,
placeholder,
} = this.props;
const uId = uniqueId("qry-");
let classNames: Array<string> = [
"ct-preferences-pane__field",
"ct-preferences-pane__field--select",
className,
];
if (block) {
classNames.push("ct-preferences-pane__field--block");
}
return (
<div className={classNames.join(" ")}>
<div className="ct-preferences-pane__field-summary">
<Heading className="ct-preferences-pane__field-heading">
<label className="ct-preferences-pane__field-label" htmlFor={uId}>
{heading}
</label>
</Heading>
<div className="ct-preferences-pane__field-description">
{description}
</div>
</div>
<div className="ct-preferences-pane__field-input">
<Select
options={options}
onChange={onChange ? this.handleChange : undefined}
onChangePromise={
onChangePromise ? this.handleChangePromise : undefined
}
value={value}
initialOptionRemoved={initialOptionRemoved}
placeholder={placeholder}
/>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
export default PreferencesPaneSelectField;
export { PreferencesPaneSelectField };
@@ -0,0 +1,43 @@
import React from "react";
import { Toggle } from "~/components/Toggle";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
interface Props {
initiallyEnabled: boolean;
onChange: (isEnabled: boolean) => void;
heading: React.ReactNode;
description: string;
}
export default class PreferencesPaneToggleField extends React.PureComponent<Props> {
static defaultProps = {
initiallyEnabled: false,
};
render() {
const { heading, description, initiallyEnabled, onChange } = this.props;
return (
<div className="ct-preferences-pane__field ct-preferences-pane__field--toggle">
<div className="ct-preferences-pane__field-heading-input">
<Heading className="ct-preferences-pane__field-heading">
{heading}
<Toggle
checked={initiallyEnabled}
onClick={onChange}
color="themed"
aria-label={description}
/>
</Heading>
</div>
<div className="ct-preferences-pane__field-summary">
{description && (
<div className="ct-preferences-pane__field-description">
{description}
</div>
)}
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import PreferencesPaneToggleField from "./PreferencesPaneToggleField";
export default PreferencesPaneToggleField;
export { PreferencesPaneToggleField };
@@ -0,0 +1,62 @@
import { orderBy } from "lodash";
import React from "react";
import { Toggle } from "~/components/Toggle";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
import { ToggleInfo } from "./typings";
interface Props {
toggles: Array<ToggleInfo>;
heading: React.ReactNode;
description: string;
}
export default class PreferencesPaneTogglesField extends React.PureComponent<Props> {
static defaultProps = {
initiallyEnabled: false,
};
render() {
const { heading, description, toggles } = this.props;
if (!toggles.length) {
return null;
}
const sortedToggles = orderBy(toggles, "sortOrder");
return (
<div className="ct-preferences-pane__field ct-preferences-pane__field--toggle">
<div className="ct-preferences-pane__field-summary">
<Heading className="ct-preferences-pane__field-heading">
{heading}
</Heading>
{description && (
<div className="ct-preferences-pane__field-description">
{description}
</div>
)}
</div>
<div className="ct-preferences-pane__field-toggles">
{sortedToggles.map((toggle, idx) => (
<div className="ct-preferences-pane__field-toggle" key={idx}>
<div className="ct-preferences-pane__field-input">
<Toggle
checked={toggle.initiallyEnabled}
onClick={toggle.onChange}
onChangePromise={toggle.onChangePromise}
color="themed"
aria-label={toggle.label}
/>
</div>
<div className="ct-preferences-pane__field-label">
{toggle.label}
</div>
</div>
))}
</div>
</div>
);
}
}
@@ -0,0 +1,5 @@
import PreferencesPaneTogglesField from "./PreferencesPaneTogglesField";
export * from "./typings";
export default PreferencesPaneTogglesField;
export { PreferencesPaneTogglesField };
@@ -0,0 +1,12 @@
import PreferencesPane from "./PreferencesPane";
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
import PreferencesPaneToggleField from "./PreferencesPaneToggleField";
import PreferencesPaneTogglesField from "./PreferencesPaneTogglesField";
export default PreferencesPane;
export {
PreferencesPane,
PreferencesPaneSelectField,
PreferencesPaneToggleField,
PreferencesPaneTogglesField,
};