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,482 @@
import React from "react";
import { useContext } from "react";
import { DispatchProp } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import {
Collapsible,
CollapsibleHeaderContent,
CollapsibleHeading,
} from "@dndbeyond/character-components/es";
import {
characterActions,
CharacterConfiguration,
CharacterCurrencyContract,
CharacterNotes,
CharacterTheme,
Constants,
ContainerLookup,
ContainerUtils,
InventoryManager,
FormatUtils,
Item,
ItemUtils,
Modifier,
RuleData,
rulesEngineSelectors,
TypeValueLookup,
CoinManager,
} from "@dndbeyond/character-rules-engine/es";
import { EditorWithDialog } from "~/subApps/builder/components/EditorWithDialog";
import { RouteKey } from "~/subApps/builder/constants";
import {
ModalData,
useModalManager,
} from "~/subApps/builder/contexts/ModalManager";
import * as toastActions from "../../../../Shared/actions/toastMessage/actions";
import { ArmorListItem } from "../../../../Shared/components/legacy/ArmorList";
import { CurrencyList } from "../../../../Shared/components/legacy/CurrencyList";
import { EquipmentManagerShop } from "../../../../Shared/components/legacy/EquipmentManagerShop";
import { GearListItem } from "../../../../Shared/components/legacy/GearList";
import { WeaponListItem } from "../../../../Shared/components/legacy/WeaponList";
import { CURRENCY_VALUE } from "../../../../Shared/constants/App";
import StartingEquipment from "../../../../Shared/containers/StartingEquipment";
import { CurrencyErrorTypeEnum } from "../../../../Shared/containers/panes/CurrencyPane/CurrencyPaneConstants";
import { CoinManagerContext } from "../../../../Shared/managers/CoinManagerContext";
import { InventoryManagerContext } from "../../../../Shared/managers/InventoryManagerContext";
import { AppNotificationUtils } from "../../../../Shared/utils";
import Page from "../../../components/Page";
import { PageBody } from "../../../components/PageBody";
import { PageHeader } from "../../../components/PageHeader";
import { BuilderAppState } from "../../../typings";
import ConnectedBuilderPage from "../ConnectedBuilderPage";
interface Props extends DispatchProp {
configuration: CharacterConfiguration;
inventory: Array<Item>;
containerLookup: ContainerLookup;
totalWeight: number;
notes: CharacterNotes;
ruleData: RuleData;
hasMaxAttunedItems: boolean;
characterId: number;
theme: CharacterTheme;
globalModifiers: Array<Modifier>;
valueLookupByType: TypeValueLookup;
proficiencyBonus: number;
inventoryManager: InventoryManager;
coinManager: CoinManager;
activeSourceCategories: Array<number>;
createModal: (modalData: ModalData) => void;
}
interface State {
showStartingEquipment: boolean;
}
class EquipmentManage extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
showStartingEquipment: props.configuration.startingEquipmentType === null,
};
}
textareaInput = React.createRef<HTMLDivElement>();
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>,
snapshot?: any
): void {
const { configuration } = this.props;
if (configuration !== prevProps.configuration) {
this.setState({
showStartingEquipment: configuration.startingEquipmentType === null,
});
}
}
handleItemAdd = (
item: Item,
amount: number,
containerDefinitionKey: string
): void => {
const { inventoryManager } = this.props;
inventoryManager.handleAdd(
{ item, amount, containerDefinitionKey },
AppNotificationUtils.handleItemAddAccepted.bind(this, item, amount)
);
};
handleCurrencyChange = (coin: CharacterCurrencyContract): void => {
const { coinManager } = this.props;
coinManager.handleCoinSet({
coin,
containerDefinitionKey: coinManager.getCharacterContainerDefinitionKey(),
});
};
handleCurrencyError = (
currencyName: string,
errorType: CurrencyErrorTypeEnum
): void => {
const { dispatch } = this.props;
let message: string = "";
if (errorType === CurrencyErrorTypeEnum.MIN) {
message =
"Cannot set currency to a negative value, the previous amount has been set instead.";
}
if (errorType === CurrencyErrorTypeEnum.MAX) {
message = `The max amount allowed for each currency type is ${FormatUtils.renderLocaleNumber(
CURRENCY_VALUE.MAX
)}, the previous value has been set instead.`;
}
if (errorType !== null) {
dispatch(
toastActions.toastError(
`Unable to Set Currency: ${currencyName}`,
message
)
);
}
};
renderStartingEquipment = (): React.ReactNode => {
const { showStartingEquipment } = this.state;
let headingNode: React.ReactNode = (
<CollapsibleHeading>Starting Equipment</CollapsibleHeading>
);
let headerNode: React.ReactNode = (
<CollapsibleHeaderContent heading={headingNode} />
);
return (
<Collapsible
header={headerNode}
className="equipment-manage__starting"
useBuilderStyles={true}
initiallyCollapsed={!showStartingEquipment}
collapsed={!showStartingEquipment}
onChangeHandler={(isCollapsed) => {
this.setState({
showStartingEquipment: !isCollapsed,
});
}}
>
<StartingEquipment
onStartingEquipmentChoose={() => {
this.setState({
showStartingEquipment: false,
});
}}
isInitialView={true}
/>
</Collapsible>
);
};
renderItem = (
item: Item,
itemParams,
weaponLabels,
armorLabels,
gearLabels
): React.ReactNode => {
if (ItemUtils.isWeaponContract(item)) {
let finalWeaponLabels = weaponLabels;
if (ItemUtils.isAmmunition(item)) {
//TODO fix this to be cleaner
finalWeaponLabels = {
...weaponLabels,
equipLabel: "Use",
unequipLabel:
weaponLabels.unequipLabel === "Stow"
? weaponLabels.unequipLabel
: "In Use",
};
}
return (
<WeaponListItem
key={uuidv4()}
item={item}
{...finalWeaponLabels}
{...itemParams}
/>
);
} else if (ItemUtils.isArmorContract(item)) {
return (
<ArmorListItem
key={uuidv4()}
item={item}
{...armorLabels}
{...itemParams}
/>
);
} else if (ItemUtils.isGearContract(item)) {
return (
<GearListItem
key={uuidv4()}
item={item}
{...gearLabels}
{...itemParams}
/>
);
}
return null;
};
renderItemList = (): React.ReactNode => {
const { inventory } = this.props;
const weaponLabels = {
equipLabel: "Wield",
unequipLabel: "Wielding",
};
const armorLabels = {
equipLabel: "Wear",
unequipLabel: "Wearing",
};
const gearLabels = {
equipLabel: "Use",
unequipLabel: "In Use",
};
const itemParams = {
showRemove: true,
showEquip: true,
showUnequip: true,
showHeaderAction: true,
};
return (
<React.Fragment>
{ItemUtils.sortInventoryItems(inventory).map((item) =>
this.renderItem(
item,
itemParams,
weaponLabels,
armorLabels,
gearLabels
)
)}
</React.Fragment>
);
};
renderInventory = (): React.ReactNode => {
const { inventory, totalWeight } = this.props;
const itemTotal: number = inventory.length;
let headerNode: React.ReactNode = (
<CollapsibleHeaderContent
heading={
<CollapsibleHeading>
Current Inventory ({itemTotal})
</CollapsibleHeading>
}
callout={
<div className="equipment-manage__callout">
Total Weight: {FormatUtils.renderWeight(totalWeight)}
</div>
}
/>
);
return (
<Collapsible
header={headerNode}
className="equipment-manage__inventory"
useBuilderStyles={true}
initiallyCollapsed={itemTotal === 0}
collapsed={itemTotal === 0}
>
{itemTotal === 0 ? (
<div className="equipment-manager__inventory-empty">
You currently have no items in your inventory. Add Starting
Equipment above or Add Items from the list of available items below.
</div>
) : (
this.renderItemList()
)}
</Collapsible>
);
};
renderOtherPossessions = (): React.ReactNode => {
const { notes, dispatch } = this.props;
return (
<Collapsible
header={"Other Possessions"}
className="equipment-manage__possessions"
useBuilderStyles={true}
>
<EditorWithDialog
heading={<h3>Personal Possessions</h3>}
editButtonLabel="Edit Possessions"
addButtonLabel="Add Possessions"
placeholder="Add personal possessions here..."
content={notes[Constants.NoteKeyEnum.PERSONAL_POSSESSIONS] ?? ""}
onSave={(content) => {
dispatch(
characterActions.noteSet(
Constants.NoteKeyEnum.PERSONAL_POSSESSIONS,
content
)
);
}}
/>
</Collapsible>
);
};
renderAddItems = (): React.ReactNode => {
const {
ruleData,
characterId,
theme,
globalModifiers,
valueLookupByType,
proficiencyBonus,
activeSourceCategories,
inventoryManager,
} = this.props;
const characterContainerKey =
ContainerUtils.getCharacterContainerDefinitionKey(characterId);
return (
<Collapsible
header={"Add Items"}
className="equipment-manage__add-items"
useBuilderStyles={true}
>
<EquipmentManagerShop
theme={theme}
globalModifiers={globalModifiers}
valueLookupByType={valueLookupByType}
onItemAdd={this.handleItemAdd}
ruleData={ruleData}
proficiencyBonus={proficiencyBonus}
containerDefinitionKey={characterContainerKey}
activeSourceCategories={activeSourceCategories}
inventoryManager={inventoryManager}
/>
</Collapsible>
);
};
renderCurrency = (): React.ReactNode => {
const { coinManager } = this.props;
const coin = coinManager.getContainerCoin(
coinManager.getCharacterContainerDefinitionKey()
);
if (!coin) {
return null;
}
let headerNode: React.ReactNode = (
<CollapsibleHeaderContent
heading={<CollapsibleHeading>Currency</CollapsibleHeading>}
callout={
<div className="equipment-manage__callout">
Total in GP:{" "}
{FormatUtils.renderLocaleNumber(
coinManager.getTotalContainerCoinInGold(
coinManager.getCharacterContainerDefinitionKey()
)
)}
</div>
}
/>
);
return (
<Collapsible
header={headerNode}
className="equipment-manage__currency"
useBuilderStyles={true}
>
<CurrencyList
{...coin}
onChange={this.handleCurrencyChange}
onError={this.handleCurrencyError}
totalGp={coinManager.getTotalContainerCoinInGold(
coinManager.getCharacterContainerDefinitionKey()
)}
/>
</Collapsible>
);
};
render() {
return (
<Page>
<PageBody>
<PageHeader>Choose Equipment</PageHeader>
<div className="equipment-manage">
{this.renderStartingEquipment()}
{this.renderInventory()}
{this.renderOtherPossessions()}
{this.renderAddItems()}
{this.renderCurrency()}
</div>
</PageBody>
</Page>
);
}
}
function EquipmentManageContainer(props) {
const { inventoryManager } = useContext(InventoryManagerContext);
const { coinManager } = useContext(CoinManagerContext);
const { createModal } = useModalManager();
return (
<EquipmentManage
coinManager={coinManager}
inventoryManager={inventoryManager}
createModal={createModal}
{...props}
/>
);
}
export default ConnectedBuilderPage(
EquipmentManageContainer,
RouteKey.EQUIPMENT_MANAGE,
(state: BuilderAppState) => {
return {
configuration: rulesEngineSelectors.getCharacterConfiguration(state),
inventory: rulesEngineSelectors.getInventory(state),
containerLookup: rulesEngineSelectors.getContainerLookup(state),
totalWeight: rulesEngineSelectors.getTotalCarriedWeight(state),
notes: rulesEngineSelectors.getCharacterNotes(state),
currencies: rulesEngineSelectors.getCurrencies(state),
ruleData: rulesEngineSelectors.getRuleData(state),
hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
characterId: rulesEngineSelectors.getId(state),
globalModifiers: rulesEngineSelectors.getValidGlobalModifiers(state),
valueLookupByType:
rulesEngineSelectors.getCharacterValueLookupByType(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
activeSourceCategories:
rulesEngineSelectors.getActiveSourceCategories(state),
};
}
);
@@ -0,0 +1,4 @@
import EquipmentManage from "./EquipmentManage";
export default EquipmentManage;
export { EquipmentManage };