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,433 @@
import React from "react";
import { connect, DispatchProp } from "react-redux";
import { Select } from "@dndbeyond/character-components/es";
import {
characterActions,
CharacterValuesContract,
Constants,
CustomProficiencyContract,
EntityRestrictionData,
HelperUtils,
HtmlSelectOption,
HtmlSelectOptionGroup,
ProficiencyGroup,
RuleData,
RuleDataUtils,
rulesEngineSelectors,
TypeValueLookup,
ValueUtils,
} from "@dndbeyond/character-rules-engine/es";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
import ProficiencyGroups from "../../../../CharacterSheet/components/ProficiencyGroups";
import { SharedAppState } from "../../../stores/typings";
import ProficienciesPaneCustomProficiency from "./ProficienciesPaneCustomProficiency";
import ProficienciesPaneExistingProficiency from "./ProficienciesPaneExistingProficiency";
export const PROFICIENCY_TYPE = {
ARMOR: 1,
WEAPON: 2,
TOOL: 3,
LANGUAGE: 4,
TOOL_OTHER: 5,
LANGUAGE_OTHER: 6,
};
interface Props extends DispatchProp {
customProficiencies: Array<CustomProficiencyContract>;
proficiencyGroups: Array<ProficiencyGroup>;
typeValueLookup: TypeValueLookup;
ruleData: RuleData;
entityRestrictionData: EntityRestrictionData;
}
interface State {
type: number | null;
subtype: number | null;
}
class ProficienciesPane extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
type: null,
subtype: null,
};
}
getSubtypeOptions = (): Array<HtmlSelectOption | HtmlSelectOptionGroup> => {
const { type } = this.state;
const { ruleData, typeValueLookup, entityRestrictionData } = this.props;
let valueType: number | null = null;
switch (type) {
case PROFICIENCY_TYPE.ARMOR:
valueType = Constants.AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL;
break;
case PROFICIENCY_TYPE.WEAPON:
valueType = Constants.AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL;
break;
case PROFICIENCY_TYPE.LANGUAGE:
valueType = Constants.AdjustmentTypeEnum.LANGUAGE_PROFICIENCY_LEVEL;
break;
case PROFICIENCY_TYPE.TOOL:
valueType = Constants.AdjustmentTypeEnum.TOOL_PROFICIENCY_LEVEL;
break;
default:
// not implemented
}
let options: Array<HtmlSelectOption | HtmlSelectOptionGroup> = [];
if (valueType !== null) {
let excludeIds = ValueUtils.getTypeValueIntIds(
typeValueLookup,
valueType
);
switch (type) {
case PROFICIENCY_TYPE.ARMOR:
options = RuleDataUtils.getArmorOptions(ruleData, excludeIds);
break;
case PROFICIENCY_TYPE.WEAPON:
options = RuleDataUtils.getWeaponOptions(ruleData, excludeIds);
break;
case PROFICIENCY_TYPE.LANGUAGE:
options = RuleDataUtils.getLanguageOptionsGroupedBySourceCategory(
ruleData,
excludeIds,
entityRestrictionData
);
break;
case PROFICIENCY_TYPE.TOOL:
options = RuleDataUtils.getToolOptions(ruleData, excludeIds);
break;
default:
// not implemented
}
}
return options;
};
getSubtypeNameLookup = (type: number): Record<number, string> => {
const { ruleData } = this.props;
let names: Record<number, string> = {};
switch (type) {
case Constants.AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL:
names = RuleDataUtils.getArmorNameLookup(ruleData);
break;
case Constants.AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL:
names = RuleDataUtils.getWeaponNameLookup(ruleData);
break;
case Constants.AdjustmentTypeEnum.LANGUAGE_PROFICIENCY_LEVEL:
names = RuleDataUtils.getLanguageNameLookup(ruleData);
break;
case Constants.AdjustmentTypeEnum.TOOL_PROFICIENCY_LEVEL:
names = RuleDataUtils.getToolNameLookup(ruleData);
break;
default:
// not implemented
}
return names;
};
handleDataUpdate = (proficiency: CharacterValuesContract): void => {
const { dispatch } = this.props;
dispatch(
characterActions.valueSet(
ValueUtils.getTypeId(proficiency),
ValueUtils.getValue(proficiency),
ValueUtils.getNotes(proficiency),
ValueUtils.getValueId(proficiency),
ValueUtils.getValueTypeId(proficiency)
)
);
};
handleProficiencyRemove = (proficiency: CharacterValuesContract): void => {
const { dispatch } = this.props;
dispatch(
characterActions.valueSet(
ValueUtils.getTypeId(proficiency),
null,
null,
ValueUtils.getValueId(proficiency),
ValueUtils.getValueTypeId(proficiency)
)
);
};
handleCustomDataUpdate = (
id: number,
properties: Partial<CustomProficiencyContract>
): void => {
const { dispatch } = this.props;
dispatch(characterActions.customProficiencySet(id, properties));
};
handleCustomRemove = (proficiency: CustomProficiencyContract): void => {
const { dispatch } = this.props;
dispatch(characterActions.customProficiencyRemove(proficiency.id));
};
handleCustomProficiencyAdd = (type: number): void => {
const { dispatch, customProficiencies } = this.props;
let label: string = "Proficiency";
let customType: number | null = null;
switch (type) {
case PROFICIENCY_TYPE.LANGUAGE_OTHER:
label = "Language";
customType = Constants.CustomProficiencyTypeEnum.LANGUAGE;
break;
case PROFICIENCY_TYPE.TOOL_OTHER:
label = "Tool";
customType = Constants.CustomProficiencyTypeEnum.TOOL;
break;
default:
// not implemented
}
if (customType !== null) {
dispatch(
characterActions.customProficiencyCreate(
`Custom ${label} ${customProficiencies.length + 1}`,
customType
)
);
}
};
handleTypeChange = (type: string): void => {
let typeValue = HelperUtils.parseInputInt(type);
switch (typeValue) {
case PROFICIENCY_TYPE.LANGUAGE_OTHER:
case PROFICIENCY_TYPE.TOOL_OTHER:
this.handleCustomProficiencyAdd(typeValue);
this.setState({
type: null,
});
break;
case PROFICIENCY_TYPE.ARMOR:
case PROFICIENCY_TYPE.WEAPON:
case PROFICIENCY_TYPE.LANGUAGE:
case PROFICIENCY_TYPE.TOOL:
default:
this.setState({
type: typeValue,
});
}
};
handleSubtypeChange = (subtype: string): void => {
const { type } = this.state;
const { dispatch, ruleData } = this.props;
let subtypeValue = HelperUtils.parseInputInt(subtype);
let valueType: number | null = null;
let valueId: number | null = null;
let valueTypeId: number | null = null;
switch (type) {
case PROFICIENCY_TYPE.ARMOR:
valueType = Constants.AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL;
valueId = subtypeValue;
valueTypeId = Constants.ItemBaseTypeIdEnum.ARMOR;
break;
case PROFICIENCY_TYPE.WEAPON:
valueType = Constants.AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL;
valueId = subtypeValue;
valueTypeId = Constants.ItemBaseTypeIdEnum.WEAPON;
break;
case PROFICIENCY_TYPE.LANGUAGE:
valueType = Constants.AdjustmentTypeEnum.LANGUAGE_PROFICIENCY_LEVEL;
valueId = subtypeValue;
valueTypeId = RuleDataUtils.getLanguageTypeId(ruleData);
break;
case PROFICIENCY_TYPE.TOOL:
valueType = Constants.AdjustmentTypeEnum.TOOL_PROFICIENCY_LEVEL;
valueId = subtypeValue;
valueTypeId = RuleDataUtils.getToolTypeId(ruleData);
break;
}
if (type !== null && valueType !== null) {
dispatch(
characterActions.valueSet(
valueType,
Constants.ProficiencyLevelEnum.FULL,
null,
ValueUtils.hack__toString(valueId),
ValueUtils.hack__toString(valueTypeId)
)
);
}
};
renderCustomProficiencies = (): React.ReactNode => {
const { typeValueLookup, customProficiencies, ruleData } = this.props;
const proficiencyGroupData = RuleDataUtils.getProficiencyGroups(ruleData);
return (
<div className="ct-proficiencies-pane__customs">
{proficiencyGroupData.map((group) => {
let proficiencies: Array<CharacterValuesContract> = [];
let custom: Array<CustomProficiencyContract> = [];
if (group.customAdjustments !== null) {
group.customAdjustments.forEach((typeId) => {
let charValues = ValueUtils.getTypeData(typeValueLookup, typeId);
proficiencies.push(...charValues);
});
}
// TODO get real data
switch (group.label) {
case "Tools":
custom = customProficiencies.filter(
(customProficiency) =>
customProficiency.type ===
Constants.CustomProficiencyTypeEnum.TOOL
);
break;
case "Languages":
custom = customProficiencies.filter(
(customProficiency) =>
customProficiency.type ===
Constants.CustomProficiencyTypeEnum.LANGUAGE
);
break;
default:
// not implemented
}
if (!proficiencies.length && !custom.length) {
return null;
}
return (
<div
className="ct-proficiencies-pane__custom"
key={group.label ? group.label : ""}
>
<div className="ct-proficiencies-pane__custom-label">
<Heading>{group.label}</Heading>
</div>
<div className="ct-proficiencies-pane__proficiencies">
{proficiencies.map((proficiency) => {
return (
<ProficienciesPaneExistingProficiency
key={ValueUtils.getUniqueKey(proficiency)}
proficiency={proficiency}
nameLookup={this.getSubtypeNameLookup(
ValueUtils.getTypeId(proficiency)
)}
onRemove={this.handleProficiencyRemove}
onDataUpdate={this.handleDataUpdate}
/>
);
})}
{custom.map((proficiency) => (
<ProficienciesPaneCustomProficiency
key={proficiency.id}
proficiency={proficiency}
onRemove={this.handleCustomRemove}
onDataUpdate={this.handleCustomDataUpdate}
/>
))}
</div>
</div>
);
})}
</div>
);
};
renderAdd = (): React.ReactNode => {
const { type, subtype } = this.state;
let typeOptions: Array<HtmlSelectOptionGroup> = [
{
optGroupLabel: "Existing",
options: [
{ label: "Armor", value: PROFICIENCY_TYPE.ARMOR },
{ label: "Weapon", value: PROFICIENCY_TYPE.WEAPON },
{ label: "Tool", value: PROFICIENCY_TYPE.TOOL },
{ label: "Language", value: PROFICIENCY_TYPE.LANGUAGE },
],
},
{
optGroupLabel: "Custom",
resetAfterChoice: true,
options: [
{ label: "Tool", value: PROFICIENCY_TYPE.TOOL_OTHER },
{ label: "Language", value: PROFICIENCY_TYPE.LANGUAGE_OTHER },
],
},
];
let subtypeOptions = this.getSubtypeOptions();
return (
<React.Fragment>
<Heading>Add New Proficiencies</Heading>
<div className="ct-proficiencies-pane__add">
<div className="ct-proficiencies-pane__add-field ct-proficiencies-pane__add-field--type">
<Select
options={typeOptions}
onChange={this.handleTypeChange}
value={type}
/>
</div>
{subtypeOptions.length > 0 && (
<div className="ct-proficiencies-pane__add-field ct-proficiencies-pane__add-field--subtype">
<Select
options={subtypeOptions}
onChange={this.handleSubtypeChange}
value={subtype}
initialOptionRemoved={true}
resetAfterChoice={true}
/>
</div>
)}
</div>
</React.Fragment>
);
};
renderProficiencyGroups = (): React.ReactNode => {
const { proficiencyGroups } = this.props;
return (
<div className="ct-proficiencies-pane__groups">
<ProficiencyGroups proficiencyGroups={proficiencyGroups} />
</div>
);
};
render() {
return (
<div className="ct-proficiencies-pane">
<Header>Proficiencies & Training</Header>
{this.renderCustomProficiencies()}
{this.renderAdd()}
{this.renderProficiencyGroups()}
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
customProficiencies: rulesEngineSelectors.getCustomProficiencies(state),
typeValueLookup: rulesEngineSelectors.getCharacterValueLookupByType(state),
ruleData: rulesEngineSelectors.getRuleData(state),
proficiencyGroups: rulesEngineSelectors.getProficiencyGroups(state),
entityRestrictionData: rulesEngineSelectors.getEntityRestrictionData(state),
};
}
export default connect(mapStateToProps)(ProficienciesPane);
@@ -0,0 +1,77 @@
import React from "react";
import { CustomProficiencyContract } from "@dndbeyond/character-rules-engine/es";
import { RemoveButton } from "../../../../components/common/Button";
import ProficienciesPaneProficiencyEditor from "../ProficienciesPaneProficiencyEditor";
interface Props {
proficiency: CustomProficiencyContract;
onDataUpdate?: (id: number, data: Record<string, any>) => void;
onRemove?: (proficiency: CustomProficiencyContract) => void;
}
export default class ProficienciesPaneCustomProficiency extends React.PureComponent<Props> {
getData = (): Record<string, any> => {
const { proficiency } = this.props;
if (proficiency === null) {
return {};
}
return proficiency;
};
handleDataUpdate = (propertyKey: string, value: string): void => {
const { onDataUpdate, proficiency } = this.props;
if (onDataUpdate) {
let newProperties = this.getData();
onDataUpdate(proficiency.id, {
...newProperties,
[propertyKey]: value,
});
}
};
handleRemove = (): void => {
const { onRemove, proficiency } = this.props;
if (onRemove) {
onRemove(proficiency);
}
};
render() {
const { proficiency } = this.props;
return (
<div className="ct-proficiencies-pane__proficiency">
<div className="ct-proficiencies-pane__proficiency-primary">
<div className="ct-proficiencies-pane__proficiency-name">
<ProficienciesPaneProficiencyEditor
onUpdate={this.handleDataUpdate}
defaultValue={proficiency.name}
propertyKey="name"
placeholder="Enter Name"
/>
</div>
<div className="ct-proficiencies-pane__proficiency-actions">
<div className="ct-proficiencies-pane__proficiency-action ct-proficiencies-pane__proficiency-action--remove">
<RemoveButton onClick={this.handleRemove}>
Remove Proficiency
</RemoveButton>
</div>
</div>
</div>
<div className="ct-proficiencies-pane__proficiency-notes">
<ProficienciesPaneProficiencyEditor
onUpdate={this.handleDataUpdate}
defaultValue={proficiency.notes}
propertyKey="notes"
placeholder="Enter Source Note..."
/>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import ProficienciesPaneCustomProficiency from "./ProficienciesPaneCustomProficiency";
export default ProficienciesPaneCustomProficiency;
export { ProficienciesPaneCustomProficiency };
@@ -0,0 +1,107 @@
import React from "react";
import {
CharacterValuesContract,
ValueUtils,
} from "@dndbeyond/character-rules-engine/es";
import { RemoveButton } from "../../../../components/common/Button";
import ProficienciesPaneProficiencyEditor from "../ProficienciesPaneProficiencyEditor";
interface Props {
proficiency: CharacterValuesContract;
nameLookup: Record<number, string>;
onDataUpdate: (proficiency: CharacterValuesContract) => void;
onRemove: (proficiency: CharacterValuesContract) => void;
}
interface State {
proficiency: CharacterValuesContract;
}
export default class ProficienciesPaneExistingProficiency extends React.PureComponent<
Props,
State
> {
constructor(props) {
super(props);
this.state = this.generateStateData(props);
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>
): void {
const { proficiency } = this.props;
if (proficiency !== prevProps.proficiency) {
this.setState(this.generateStateData(this.props));
}
}
generateStateData = (props: Props): State => {
const { proficiency } = props;
return {
proficiency,
};
};
handleDataUpdate = (propertyKey: string, value) => {
const { onDataUpdate } = this.props;
this.setState((prevState) => {
let newProficiency = {
...prevState.proficiency,
[propertyKey]: value,
};
if (onDataUpdate) {
onDataUpdate(newProficiency);
}
return {
proficiency: newProficiency,
};
});
};
handleRemove = () => {
const { proficiency } = this.state;
const { onRemove } = this.props;
if (onRemove) {
onRemove(proficiency);
}
};
render() {
const { proficiency } = this.state;
const { nameLookup } = this.props;
const valueId = ValueUtils.getValueId(proficiency);
return (
<div className="ct-proficiencies-pane__proficiency">
<div className="ct-proficiencies-pane__proficiency-primary">
<div className="ct-proficiencies-pane__proficiency-name">
{valueId === null ? "" : nameLookup[valueId]}
</div>
<div className="ct-proficiencies-pane__proficiency-actions">
<div className="ct-proficiencies-pane__proficiency-action ct-proficiencies-pane__proficiency-action--remove">
<RemoveButton onClick={this.handleRemove}>
Remove Proficiency
</RemoveButton>
</div>
</div>
</div>
<div className="ct-proficiencies-pane__proficiency-notes">
<ProficienciesPaneProficiencyEditor
onUpdate={this.handleDataUpdate}
defaultValue={ValueUtils.getNotes(proficiency)}
propertyKey="notes"
placeholder="Enter Source Note..."
/>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import ProficienciesPaneExistingProficiency from "./ProficienciesPaneExistingProficiency";
export default ProficienciesPaneExistingProficiency;
export { ProficienciesPaneExistingProficiency };
@@ -0,0 +1,45 @@
import React from "react";
interface Props {
label?: string;
propertyKey: string;
defaultValue: string | null;
onUpdate?: (propertyKey: string, value: string) => void;
placeholder: string;
}
export default class ProficienciesPaneProficiencyEditor extends React.PureComponent<Props> {
static defaultProps = {
placeholder: "",
};
handleBlur = (evt: React.FocusEvent<HTMLInputElement>): void => {
const { propertyKey, onUpdate } = this.props;
if (onUpdate) {
onUpdate(propertyKey, evt.target.value);
}
};
render() {
const { label, defaultValue, placeholder } = this.props;
return (
<div className="ct-proficiencies-pane__proficiency-editor">
<div className="ct-proficiencies-pane__proficiency-editor-field">
<input
type="text"
defaultValue={defaultValue === null ? "" : defaultValue}
onBlur={this.handleBlur}
className="ct-proficiencies-pane__proficiency-editor-input"
placeholder={placeholder}
/>
</div>
{label && (
<div className="ct-proficiencies-pane__proficiency-editor-label">
{label}
</div>
)}
</div>
);
}
}
@@ -0,0 +1,4 @@
import ProficienciesPaneProficiencyEditor from "./ProficienciesPaneProficiencyEditor";
export default ProficienciesPaneProficiencyEditor;
export { ProficienciesPaneProficiencyEditor };
@@ -0,0 +1,12 @@
import ProficienciesPane from "./ProficienciesPane";
import ProficienciesPaneCustomProficiency from "./ProficienciesPaneCustomProficiency";
import ProficienciesPaneExistingProficiency from "./ProficienciesPaneExistingProficiency";
import ProficienciesPaneProficiencyEditor from "./ProficienciesPaneProficiencyEditor";
export default ProficienciesPane;
export {
ProficienciesPane,
ProficienciesPaneCustomProficiency,
ProficienciesPaneExistingProficiency,
ProficienciesPaneProficiencyEditor,
};