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,344 @@
import React from "react";
import { connect, DispatchProp } from "react-redux";
import { Collapsible } from "@dndbeyond/character-components/es";
import {
AbilityLookup,
Action,
ActionUtils,
ActivationUtils,
characterActions,
CharacterTheme,
Constants,
CustomActionContract,
DiceUtils,
EntityValueLookup,
InventoryLookup,
RuleData,
RuleDataUtils,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import {
PaneComponentEnum,
PaneIdentifiersCustomAction,
} from "~/subApps/sheet/components/Sidebar/types";
import { PaneInitFailureContent } from "../../../../../../subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
import { ActionDetail } from "../../../components/ActionDetail";
import CustomizeDataEditor from "../../../components/CustomizeDataEditor";
import EditorBox from "../../../components/EditorBox";
import { RemoveButton } from "../../../components/common/Button";
import { appEnvSelectors } from "../../../selectors";
import { SharedAppState } from "../../../stores/typings";
type EditCustomContractProperties = Omit<
CustomActionContract,
"id" | "entityTypeId"
>;
interface Props extends DispatchProp {
actions: Array<Action>;
entityValueLookup: EntityValueLookup;
abilityLookup: AbilityLookup;
inventoryLookup: InventoryLookup;
ruleData: RuleData;
identifiers: PaneIdentifiersCustomAction | null;
isReadonly: boolean;
proficiencyBonus: number;
theme: CharacterTheme;
paneContext: PaneInfo;
}
interface State {
action: Action | null;
}
class CustomActionPane extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = this.generateStateData(props);
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>
): void {
const { actions, identifiers } = this.props;
if (
actions !== prevProps.actions ||
identifiers !== prevProps.identifiers
) {
this.setState(this.generateStateData(this.props));
}
}
generateStateData = (props: Props): State => {
const { actions, identifiers } = props;
let foundAction: Action | null | undefined = null;
if (identifiers !== null) {
foundAction = actions.find((action) => identifiers.id === action.id);
}
return {
action: foundAction ? foundAction : null,
};
};
handleCustomDataUpdate = (data: EditCustomContractProperties): void => {
const { action } = this.state;
const { dispatch } = this.props;
if (action === null) {
return;
}
const mappingId = ActionUtils.getMappingId(action);
if (mappingId !== null) {
dispatch(characterActions.customActionSet(mappingId, data));
}
};
handleCustomActionsShow = (): void => {
const {
paneContext: { paneHistoryPush },
} = this.props;
paneHistoryPush(PaneComponentEnum.CUSTOM_ACTIONS);
};
handleRemove = (): void => {
const { action } = this.state;
const {
dispatch,
paneContext: { paneHistoryStart },
} = this.props;
paneHistoryStart(PaneComponentEnum.CUSTOM_ACTIONS);
if (action) {
const mappingId = ActionUtils.getMappingId(action);
if (mappingId !== null) {
dispatch(characterActions.customActionRemove(mappingId));
}
}
};
getData = (): Record<string, any> => {
const { action } = this.state;
if (action === null) {
return {};
}
let activation = ActionUtils.getDefinitionActivation(action);
let range = ActionUtils.getDefinitionRange(action);
let dice = ActionUtils.getDefinitionDice(action);
let value = ActionUtils.getDefinitionFixedValue(action);
let diceCount: number | null = null;
let diceType: number | null = null;
let fixedValue: number | null = null;
if (dice !== null) {
diceCount = DiceUtils.getCount(dice);
diceType = DiceUtils.getValue(dice);
fixedValue = DiceUtils.getFixedValue(dice);
} else if (value !== null) {
fixedValue = value;
}
let data: EditCustomContractProperties = {
actionType: ActionUtils.getDefinitionActionTypeId(action),
activationTime: ActivationUtils.getTime(activation),
activationType: ActivationUtils.getType(activation),
aoeSize: range?.aoeSize ?? null,
aoeType: range?.aoeType ?? null,
attackSubtype: ActionUtils.getDefinitionAttackSubtypeId(action),
damageBonus: null, // not used
damageTypeId: ActionUtils.getDefinitionDamageTypeId(action),
description: ActionUtils.getDefinitionDescription(action),
diceCount,
diceType,
fixedValue,
displayAsAttack: ActionUtils.displayAsAttack(action),
fixedSaveDc: ActionUtils.getDefinitionFixedSaveDc(action),
isMartialArts: ActionUtils.getDefinitionIsMartialArts(action),
isOffhand: ActionUtils.isOffhand(action),
isProficient: ActionUtils.isProficient(action),
isSilvered: ActionUtils.isSilvered(action),
longRange: range?.longRange ?? null,
name: ActionUtils.getDefinitionName(action),
onMissDescription: ActionUtils.getDefinitionOnMissDescription(action),
range: range?.range ?? null,
rangeId: ActionUtils.getDefinitionAttackRangeId(action),
saveFailDescription: ActionUtils.getDefinitionSaveFailDescription(action),
saveStatId: ActionUtils.getDefinitionSaveStatId(action),
saveSuccessDescription:
ActionUtils.getDefinitionSaveSuccessDescription(action),
snippet: ActionUtils.getDefinitionSnippet(action),
spellRangeType: ActionUtils.getDefinitionSpellRangeType(action),
statId: ActionUtils.getDefinitionAbilityModifierStatId(action),
toHitBonus: null, // not used
};
return data;
};
renderCustomize = (): React.ReactNode => {
const { isReadonly, ruleData } = this.props;
const { action } = this.state;
if (isReadonly) {
return null;
}
if (action === null) {
return null;
}
const damageTypeOptions = RuleDataUtils.getDamageTypeOptions(ruleData);
const rangeTypeOptions = RuleDataUtils.getAttackRangeTypeOptions(ruleData);
const statOptions = RuleDataUtils.getStatOptions(ruleData);
const dieTypeOptions = RuleDataUtils.getDieTypeOptions(ruleData);
const attackSubtypeOptions =
RuleDataUtils.getAttackSubtypeOptions(ruleData);
const activationTypeOptions =
RuleDataUtils.getActivationTypeOptions(ruleData);
const aoeTypeOptions = RuleDataUtils.getAoeTypeOptions(ruleData);
const spellRangeTypeOptions =
RuleDataUtils.getSpellRangeTypeOptions(ruleData);
const actionType = ActionUtils.getActionTypeId(action);
return (
<Collapsible
layoutType={"minimal"}
header="Edit"
className="ct-custom-action-pane__customize"
>
<EditorBox>
<CustomizeDataEditor
data={this.getData()}
fallbackValues={{
displayAsAttack: ActionUtils.isDefaultDisplayAsAttack(action),
}}
labelOverrides={{
fixedValue: "Fixed Value",
}}
enableName={true}
enableIsProficient={true}
enableRangeType={true}
enableDiceCount={true}
enableDiceType={true}
enableFixedValue={true}
enableDamageType={true}
enableIsOffhand={actionType === Constants.ActionTypeEnum.WEAPON}
enableAttackSubtype={actionType === Constants.ActionTypeEnum.WEAPON}
enableIsSilver={actionType === Constants.ActionTypeEnum.WEAPON}
enableIsMartialArts={true}
enableDescription={true}
enableStat={true}
enableSaveDc={true}
enableActivationTime={true}
enableActivationType={true}
enableSaveType={true}
enableDisplayAsAttack={true}
enableAoeType={true}
enableAoeSize={true}
enableSpellRangeType={actionType === Constants.ActionTypeEnum.SPELL}
enableRange={true}
enableLongRange={actionType === Constants.ActionTypeEnum.WEAPON}
enableSnippet={true}
maxNameLength={1024}
rangeOptions={rangeTypeOptions}
damageTypeOptions={damageTypeOptions}
diceTypeOptions={dieTypeOptions}
statOptions={statOptions}
attackSubtypeOptions={attackSubtypeOptions}
activationTypeOptions={activationTypeOptions}
aoeTypeOptions={aoeTypeOptions}
spellRangeTypeOptions={spellRangeTypeOptions}
onDataUpdate={this.handleCustomDataUpdate}
/>
</EditorBox>
</Collapsible>
);
};
render() {
const { action } = this.state;
const {
ruleData,
entityValueLookup,
abilityLookup,
inventoryLookup,
isReadonly,
theme,
proficiencyBonus,
} = this.props;
if (action === null) {
return <PaneInitFailureContent />;
}
return (
<div
className="ct-custom-action-pane"
key={ActionUtils.getUniqueKey(action)}
>
<Header parent="Custom Actions" onClick={this.handleCustomActionsShow}>
{ActionUtils.getName(action)}
</Header>
{this.renderCustomize()}
<ActionDetail
theme={theme}
action={action}
ruleData={ruleData}
entityValueLookup={entityValueLookup}
inventoryLookup={inventoryLookup}
showCustomize={false}
abilityLookup={abilityLookup}
isReadonly={isReadonly}
proficiencyBonus={proficiencyBonus}
/>
{!isReadonly && (
<div className="ct-custom-action-pane__actions">
<div
className="ct-custom-action-pane__action ct-custom-action-pane__action--remove"
onClick={this.handleRemove}
>
<RemoveButton onClick={this.handleRemove}>
Remove Action
</RemoveButton>
</div>
</div>
)}
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
actions: rulesEngineSelectors.getCustomActions(state),
ruleData: rulesEngineSelectors.getRuleData(state),
entityValueLookup:
rulesEngineSelectors.getCharacterValueLookupByEntity(state),
inventoryLookup: rulesEngineSelectors.getInventoryLookup(state),
abilityLookup: rulesEngineSelectors.getAbilityLookup(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
const CustomActionPaneContainer = (props) => {
const { pane } = useSidebar();
return <CustomActionPane paneContext={pane} {...props} />;
};
export default connect(mapStateToProps)(CustomActionPaneContainer);
@@ -0,0 +1,4 @@
import CustomActionPane from "./CustomActionPane";
export default CustomActionPane;
export { CustomActionPane };