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,581 @@
import { sortBy } from "lodash";
import React from "react";
import { connect, DispatchProp } from "react-redux";
import {
Collapsible,
DamageTypeIcon,
DataOriginName,
Select,
ComponentConstants,
} from "@dndbeyond/character-components/es";
import {
characterActions,
CharacterDefenseAdjustmentContract,
ConditionContract,
ConditionDefinitionContract,
ConditionUtils,
Constants,
DamageAdjustmentContract,
DataOrigin,
DefenseAdjustment,
DefenseAdjustmentContract,
HelperUtils,
HtmlSelectOption,
HtmlSelectOptionGroup,
RuleData,
RuleDataUtils,
rulesEngineSelectors,
CharacterTheme,
} 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 { getDataOriginComponentInfo } from "~/subApps/sheet/components/Sidebar/helpers/paneUtils";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import * as appEnvSelectors from "../../../selectors/appEnv";
import { SharedAppState } from "../../../stores/typings";
import DefenseManagePaneCustomItem from "./DefenseManagePaneCustomItem";
const DEFENSE_ADJUSTMENT_TYPE_OPTION = {
RESISTANCE: 1,
IMMUNITY: 2,
VULNERABILITY: 3,
};
interface AdjustmentGroupEntry extends DefenseAdjustmentContract {
idx: number;
definition: DamageAdjustmentContract | ConditionDefinitionContract;
}
interface AdjustmentGroupInfo {
label: string;
adjustments: Array<AdjustmentGroupEntry>;
}
interface Props extends DispatchProp {
resistances: Array<DefenseAdjustment>;
immunities: Array<DefenseAdjustment>;
vulnerabilities: Array<DefenseAdjustment>;
resistanceData: Array<DamageAdjustmentContract>;
vulnerabilityData: Array<DamageAdjustmentContract>;
conditionImmunityData: Array<ConditionContract>;
damageImmunityData: Array<DamageAdjustmentContract>;
customDefenseAdjustments: Array<CharacterDefenseAdjustmentContract>;
ruleData: RuleData;
isReadonly: boolean;
theme: CharacterTheme;
paneHistoryPush: PaneInfo["paneHistoryPush"];
}
interface State {
adjustmentType: number | null;
adjustmentSubType: string | null;
}
class DefenseManagePane extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
adjustmentType: null,
adjustmentSubType: null,
};
}
getNewDamageAdjustments = (): Array<CharacterDefenseAdjustmentContract> => {
return this.props.customDefenseAdjustments.filter(
(newData) =>
newData.type === Constants.DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT
);
};
getNewConditionAdjustments =
(): Array<CharacterDefenseAdjustmentContract> => {
return this.props.customDefenseAdjustments.filter(
(newData) =>
newData.type ===
Constants.DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT
);
};
getAvailableResistances = (): Array<DamageAdjustmentContract> => {
const { resistanceData } = this.props;
const newDefenseAdjustments = this.getNewDamageAdjustments();
return resistanceData
.filter((data) => !data.isMulti)
.filter((data) =>
newDefenseAdjustments.every(
(newData) => newData.adjustmentId !== data.id
)
);
};
getAvailableVulnerabilities = (): Array<DamageAdjustmentContract> => {
const { vulnerabilityData } = this.props;
const newDefenseAdjustments = this.getNewDamageAdjustments();
return vulnerabilityData
.filter((data) => !data.isMulti)
.filter((data) =>
newDefenseAdjustments.every(
(newData) => newData.adjustmentId !== data.id
)
);
};
getAvailableDamageImmunities = (): Array<DamageAdjustmentContract> => {
const { damageImmunityData } = this.props;
const newDefenseAdjustments = this.getNewDamageAdjustments();
return damageImmunityData
.filter((data) => !data.isMulti)
.filter((data) =>
newDefenseAdjustments.every(
(newData) => newData.adjustmentId !== data.id
)
);
};
getAvailableConditionImmunities = (): Array<ConditionContract> => {
const { conditionImmunityData } = this.props;
const newDefenseAdjustments = this.getNewConditionAdjustments();
return conditionImmunityData.filter((data) =>
newDefenseAdjustments.every(
(newData) => newData.adjustmentId !== ConditionUtils.getId(data)
)
);
};
handleAdjustmentRemove = (type: number, id: number): void => {
const { dispatch } = this.props;
dispatch(characterActions.customDefenseAdjustmentRemove(type, id));
};
handleAdjustmentSourceUpdate = (
type: number,
id: number,
source: string | null
) => {
const { dispatch, customDefenseAdjustments } = this.props;
let foundAdjustment = customDefenseAdjustments.find(
(adjustment) => adjustment.type === type && adjustment.adjustmentId === id
);
if (foundAdjustment) {
dispatch(
characterActions.customDefenseAdjustmentSet({
...foundAdjustment,
source,
})
);
}
};
handleChangeAdjustmentType = (value: string): void => {
this.setState({
adjustmentType: HelperUtils.parseInputInt(value),
adjustmentSubType: null,
});
};
handleChangeAdjustmentSubType = (subTypeValue: string): void => {
const { adjustmentType } = this.state;
const { dispatch } = this.props;
let type: number | null = null;
let adjustmentId: number | null = null;
switch (adjustmentType) {
case DEFENSE_ADJUSTMENT_TYPE_OPTION.RESISTANCE:
case DEFENSE_ADJUSTMENT_TYPE_OPTION.VULNERABILITY:
type = Constants.DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT;
adjustmentId = parseInt(subTypeValue);
break;
case DEFENSE_ADJUSTMENT_TYPE_OPTION.IMMUNITY:
let [defenseTypeKey, defenseAdjustmentId] = subTypeValue.split("-");
type = parseInt(defenseTypeKey);
adjustmentId = parseInt(defenseAdjustmentId);
break;
default:
// not implemented
}
if (type !== null && adjustmentId !== null) {
let newAdjustment: CharacterDefenseAdjustmentContract = {
type,
adjustmentId,
source: null,
};
dispatch(characterActions.customDefenseAdjustmentAdd(newAdjustment));
}
};
handleDataOriginDetailShow = (dataOrigin: DataOrigin): void => {
const { paneHistoryPush } = this.props;
let component = getDataOriginComponentInfo(dataOrigin);
if (component.type !== PaneComponentEnum.ERROR_404) {
paneHistoryPush(component.type, component.identifiers);
}
};
renderExtraDataOrigin = (dataOrigin: DataOrigin): React.ReactNode => {
const { theme } = this.props;
return (
<span className="ct-defense-manage-pane__item-extra">
(
<DataOriginName
dataOrigin={dataOrigin}
onClick={this.handleDataOriginDetailShow}
theme={theme}
/>
)
</span>
);
};
getDefenseAdjustmentIcon = (
type: Constants.DefenseAdjustmentTypeEnum,
slug: string
): React.ReactNode => {
const { theme } = this.props;
let iconClassNames: Array<string> = [
"ct-defense-manage-pane__item-icon",
`ct-defense-manage-pane__item-icon--${slug}`,
];
switch (type) {
case Constants.DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT:
if (theme.isDarkMode) {
iconClassNames.push(`i-condition-white-${slug}`);
} else {
iconClassNames.push(`i-condition-${slug}`);
}
return <i className={iconClassNames.join(" ")} />;
case Constants.DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT:
return (
<DamageTypeIcon
type={slug as ComponentConstants.DamageTypePropType}
theme={theme}
/>
);
default:
return null;
}
};
renderDamageAdjustmentList = (
defenseAdjustments: Array<DefenseAdjustment>
): React.ReactNode => {
return defenseAdjustments.map((defenseAdjustment, idx) => {
const { dataOrigin, name, isCustom, slug, type } = defenseAdjustment;
return (
<div className="ct-defense-manage-pane__item" key={idx}>
<div className="ct-defense-manage-pane__item-value">
{this.getDefenseAdjustmentIcon(type, slug)}
</div>
<div className="ct-defense-manage-pane__item-label">
<span className="ct-defense-manage-pane__item-label-text">
{name}
{isCustom ? "*" : ""}
</span>
{this.renderExtraDataOrigin(dataOrigin)}
</div>
</div>
);
});
//`
};
renderDamageAdjustmentGroup = (
label: string,
defenseAdjustments: Array<DefenseAdjustment>
): React.ReactNode => {
if (!defenseAdjustments.length) {
return null;
}
return (
<div className="ct-defense-manage-pane__group">
<div className="ct-defense-manage-pane__heading">{label}</div>
<div className="ct-defense-manage-pane__group-items">
{this.renderDamageAdjustmentList(defenseAdjustments)}
</div>
</div>
);
};
renderCustomDefenseAdjustmentList = (): React.ReactNode => {
const { customDefenseAdjustments, ruleData } = this.props;
const damageAdjustmentDataLookup =
RuleDataUtils.getDamageAdjustmentsLookup(ruleData);
const conditionDataLookup = RuleDataUtils.getConditionLookup(ruleData);
let adjustmentGroups: Array<AdjustmentGroupInfo> = [
{ label: "Resistances", adjustments: [] },
{ label: "Immunities", adjustments: [] },
{ label: "Vulnerabilities", adjustments: [] },
];
customDefenseAdjustments.forEach((defenseAdjustment, newDefIdx) => {
const { type, adjustmentId, source } = defenseAdjustment;
let groupIdx: number | null = null;
let definition:
| ConditionDefinitionContract
| DamageAdjustmentContract
| null = null;
switch (type) {
case Constants.DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT:
definition = HelperUtils.lookupDataOrFallback(
damageAdjustmentDataLookup,
adjustmentId
);
if (definition !== null) {
switch (definition.type) {
case Constants.DamageAdjustmentTypeEnum.RESISTANCE:
groupIdx = 0;
break;
case Constants.DamageAdjustmentTypeEnum.IMMUNITY:
groupIdx = 1;
break;
case Constants.DamageAdjustmentTypeEnum.VULNERABILITY:
groupIdx = 2;
break;
}
}
break;
case Constants.DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT:
let conditionData = HelperUtils.lookupDataOrFallback(
conditionDataLookup,
adjustmentId
);
if (conditionData !== null) {
definition = conditionData.definition;
groupIdx = 1;
}
break;
default:
// not implemented
}
if (groupIdx !== null && definition !== null) {
adjustmentGroups[groupIdx].adjustments.push({
id: defenseAdjustment.adjustmentId,
type: defenseAdjustment.type,
source: defenseAdjustment.source,
idx: newDefIdx,
definition,
});
}
});
return adjustmentGroups.map((adjustmentGroup, groupIdx) => {
if (!adjustmentGroup.adjustments.length) {
return null;
}
let sortedAdjustments: Array<AdjustmentGroupEntry> = sortBy(
adjustmentGroup.adjustments,
[(adjustment: any) => adjustment.definition.name]
);
return (
<div className="ct-defense-manage-pane__custom-group" key={groupIdx}>
<div className="ct-defense-manage-pane__heading">
{adjustmentGroup.label}
</div>
{sortedAdjustments.map((defenseAdjustment) => {
const { type, id, source, definition } = defenseAdjustment;
return (
<DefenseManagePaneCustomItem
key={`${type}-${id}`}
id={id}
type={type}
initialValue={source}
label={definition.name ? definition.name : ""}
onRemove={this.handleAdjustmentRemove}
onSourceUpdate={this.handleAdjustmentSourceUpdate}
/>
);
})}
</div>
);
});
//`
};
renderCustomUi = (): React.ReactNode => {
const { adjustmentType } = this.state;
const { isReadonly } = this.props;
if (isReadonly) {
return;
}
const adjustmentTypeOptions: Array<HtmlSelectOption> = [
{ label: "Resistance", value: DEFENSE_ADJUSTMENT_TYPE_OPTION.RESISTANCE },
{ label: "Immunity", value: DEFENSE_ADJUSTMENT_TYPE_OPTION.IMMUNITY },
{
label: "Vulnerability",
value: DEFENSE_ADJUSTMENT_TYPE_OPTION.VULNERABILITY,
},
];
let adjustmentSubTypeOptions: Array<
HtmlSelectOptionGroup | HtmlSelectOption
> = [];
let subTypePlaceholder: string = "";
switch (adjustmentType) {
case DEFENSE_ADJUSTMENT_TYPE_OPTION.RESISTANCE:
adjustmentSubTypeOptions = this.getAvailableResistances().map(
(data) => ({
label: data.name,
value: data.id,
})
);
subTypePlaceholder = "-- Choose a Resistance --";
break;
case DEFENSE_ADJUSTMENT_TYPE_OPTION.VULNERABILITY:
adjustmentSubTypeOptions = this.getAvailableVulnerabilities().map(
(data) => ({
label: data.name,
value: data.id,
})
);
subTypePlaceholder = "-- Choose a Vulnerability --";
break;
case DEFENSE_ADJUSTMENT_TYPE_OPTION.IMMUNITY:
adjustmentSubTypeOptions = [
{
optGroupLabel: "Damage",
options: this.getAvailableDamageImmunities().map((data) => ({
label: data.name,
value: `${Constants.DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT}-${data.id}`,
})),
},
{
optGroupLabel: "Conditions",
options: this.getAvailableConditionImmunities().map((data) => ({
label: ConditionUtils.getName(data),
value: `${
Constants.DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT
}-${ConditionUtils.getUniqueKey(data)}`,
})),
},
];
subTypePlaceholder = "-- Choose an Immunity --";
//`
break;
default:
// not implemented
}
return (
<Collapsible
layoutType={"minimal"}
header="Customize"
className="ct-defense-manage-pane__custom"
>
<div className="ct-defense-manage-pane__custom-fields">
<div className="dct-defense-manage-pane__custom-field">
<Select
options={adjustmentTypeOptions}
onChange={this.handleChangeAdjustmentType}
value={adjustmentType}
/>
</div>
<div className="ct-defense-manage-pane__custom-field">
{adjustmentType !== null && (
<Select
options={sortBy(adjustmentSubTypeOptions, "label")}
onChange={this.handleChangeAdjustmentSubType}
resetAfterChoice={true}
placeholder={subTypePlaceholder}
value={null}
/>
)}
</div>
</div>
<div className="ct-defense-manage-pane__custom-items">
{this.renderCustomDefenseAdjustmentList()}
</div>
</Collapsible>
);
};
renderDefault = (): React.ReactNode => {
return (
<div className="ct-defense-manage-pane__default">
No Resistances, Immunities, or Vulnerabilities
</div>
);
};
renderDefenseGroups = (): React.ReactNode => {
const { resistances, vulnerabilities, immunities } = this.props;
return (
<React.Fragment>
{this.renderDamageAdjustmentGroup("Resistances", resistances)}
{this.renderDamageAdjustmentGroup("Immunities", immunities)}
{this.renderDamageAdjustmentGroup("Vulnerabilities", vulnerabilities)}
</React.Fragment>
);
};
render() {
const { resistances, vulnerabilities, immunities } = this.props;
let contentNode: React.ReactNode;
if (!resistances.length && !vulnerabilities.length && !immunities.length) {
contentNode = this.renderDefault();
} else {
contentNode = this.renderDefenseGroups();
}
return (
<div className="ct-defense-manage-pane">
<Header>Defenses</Header>
{contentNode}
{this.renderCustomUi()}
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
resistances: rulesEngineSelectors.getActiveResistances(state),
immunities: rulesEngineSelectors.getActiveImmunities(state),
vulnerabilities: rulesEngineSelectors.getActiveVulnerabilities(state),
resistanceData: rulesEngineSelectors.getResistanceData(state),
vulnerabilityData: rulesEngineSelectors.getVulnerabilityData(state),
conditionImmunityData: rulesEngineSelectors.getConditionImmunityData(state),
damageImmunityData: rulesEngineSelectors.getDamageImmunityData(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
ruleData: rulesEngineSelectors.getRuleData(state),
customDefenseAdjustments:
rulesEngineSelectors.getCustomDefenseAdjustments(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
};
}
const DefenseManagePaneContainer = (props) => {
const {
pane: { paneHistoryPush },
} = useSidebar();
return <DefenseManagePane {...props} paneHistoryPush={paneHistoryPush} />;
};
export default connect(mapStateToProps)(DefenseManagePaneContainer);
@@ -0,0 +1,84 @@
import React from "react";
import { RemoveButton } from "../../../../components/common/Button";
interface Props {
label: string;
type: number;
id: number;
initialValue: string | null;
sourcePlaceholder: string;
onRemove?: (type: number, id: number) => void;
onSourceUpdate?: (type: number, id: number, value: string | null) => void;
}
interface State {
value: string | null;
isDirty: boolean;
}
export default class DefenseManagePaneCustomItem extends React.PureComponent<
Props,
State
> {
static defaultProps = {
sourcePlaceholder: "Enter Source Note...",
};
constructor(props: Props) {
super(props);
this.state = {
value: props.initialValue,
isDirty: false,
};
}
handleRemove = (): void => {
const { type, id, onRemove } = this.props;
if (onRemove) {
onRemove(type, id);
}
};
handleSourceBlur = (evt: React.FocusEvent<HTMLInputElement>): void => {
const { value, isDirty } = this.state;
const { type, id, onSourceUpdate } = this.props;
if (isDirty && onSourceUpdate) {
onSourceUpdate(type, id, value);
this.setState({
isDirty: false,
});
}
};
handleSourceChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
value: evt.target.value,
isDirty: true,
});
};
render() {
const { value } = this.state;
const { label, sourcePlaceholder } = this.props;
return (
<div className="ct-defense-manage-pane__custom-item">
<div className="ct-defense-manage-pane__custom-item-action">
<RemoveButton onClick={this.handleRemove} />
</div>
<div className="ct-defense-manage-pane__custom-item-label">{label}</div>
<div className="ct-defense-manage-pane__custom-item-source">
<input
type="text"
onChange={this.handleSourceChange}
onBlur={this.handleSourceBlur}
value={value === null ? "" : value}
placeholder={sourcePlaceholder}
/>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import DefenseManagePaneCustomItem from "./DefenseManagePaneCustomItem";
export default DefenseManagePaneCustomItem;
export { DefenseManagePaneCustomItem };
@@ -0,0 +1,5 @@
import DefenseManagePane from "./DefenseManagePane";
import DefenseManagePaneCustomItem from "./DefenseManagePaneCustomItem";
export default DefenseManagePane;
export { DefenseManagePane, DefenseManagePaneCustomItem };