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,513 @@
import { orderBy } from "lodash";
import React from "react";
import { useContext } from "react";
import { connect, DispatchProp } from "react-redux";
import { Collapsible } from "@dndbeyond/character-components/es";
import {
ApiAdapterPromise,
ApiAdapterRequestConfig,
ApiResponse,
BaseItemDefinitionContract,
CharacterTheme,
Constants,
Container,
ContainerLookup,
ContainerUtils,
DataOrigin,
EntityValueLookup,
InventoryManager,
Infusion,
InfusionChoiceLookup,
InfusionUtils,
Item,
ItemUtils,
Modifier,
PartyInfo,
RuleData,
rulesEngineSelectors,
serviceDataSelectors,
SnippetData,
Spell,
TypeValueLookup,
WeaponSpellDamageGroup,
ItemManager,
ContainerManager,
CharacterCurrencyContract,
FormatUtils,
CoinManager,
} from "@dndbeyond/character-rules-engine/es";
import { EditableName } from "~/components/EditableName";
import { ItemName } from "~/components/ItemName";
import { Link } from "~/components/Link";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { Preview } from "~/subApps/sheet/components/Sidebar/components/Preview";
import {
getDataOriginComponentInfo,
getSpellComponentInfo,
} from "~/subApps/sheet/components/Sidebar/helpers/paneUtils";
import {
PaneComponentEnum,
PaneIdentifiersContainer,
} from "~/subApps/sheet/components/Sidebar/types";
import { PaneInitFailureContent } from "../../../../../../subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
import { CURRENCY_VALUE } from "../../../../Shared/constants/App";
import { CoinManagerContext } from "../../../../Shared/managers/CoinManagerContext";
import * as toastActions from "../../../actions/toastMessage/actions";
import ContainerActions from "../../../components/ContainerActions";
import ItemDetail from "../../../components/ItemDetail";
import { InventoryManagerContext } from "../../../managers/InventoryManagerContext";
import { appEnvSelectors } from "../../../selectors";
import { SharedAppState } from "../../../stores/typings";
import { AppNotificationUtils, PaneIdentifierUtils } from "../../../utils";
import ItemDetailActions from "../../ItemDetailActions";
import { CurrencyErrorTypeEnum } from "../CurrencyPane/CurrencyPaneConstants";
interface Props extends DispatchProp {
weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
ruleData: RuleData;
snippetData: SnippetData;
entityValueLookup: EntityValueLookup;
infusionChoiceLookup: InfusionChoiceLookup;
identifiers: PaneIdentifiersContainer | null;
isReadonly: boolean;
proficiencyBonus: number;
theme: CharacterTheme;
containers: Array<Container>;
globalModifiers: Array<Modifier>;
valueLookupByType: TypeValueLookup;
inventory: Array<Item>;
containerLookup: ContainerLookup;
partyInfo: PartyInfo | null;
inventoryManager: InventoryManager;
coinManager: CoinManager;
paneHistoryPush: PaneInfo["paneHistoryPush"];
}
interface State {
item: ItemManager | null;
currentContainer: ContainerManager | null;
isCustomizeClosed: boolean;
}
class ContainerPane extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = this.generateStateData(props, true);
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>
): void {
const { inventory, identifiers, containerLookup } = this.props;
if (
inventory !== prevProps.inventory ||
containerLookup !== prevProps.containerLookup ||
identifiers !== prevProps.identifiers
) {
this.setState(
this.generateStateData(this.props, prevState.isCustomizeClosed)
);
}
}
generateStateData = (props: Props, isCustomizeClosed: boolean): State => {
const { identifiers, inventoryManager } = props;
let foundItem: ItemManager | null = null;
let foundContainer: ContainerManager | null = null;
if (identifiers?.containerDefinitionKey) {
foundContainer = inventoryManager.getContainer(
identifiers.containerDefinitionKey
);
foundItem = foundContainer ? foundContainer.getContainerItem() : null;
}
return {
item: foundItem ?? null,
currentContainer: foundContainer ?? null,
isCustomizeClosed,
};
};
handleCustomDataUpdate = (
adjustmentType: Constants.AdjustmentTypeEnum,
value: any
): void => {
const { item } = this.state;
const { inventoryManager } = this.props;
if (item) {
inventoryManager.handleCustomizationSet({
item: item.item,
adjustmentType,
value,
});
}
};
handleRemoveCustomizations = (): void => {
const { item } = this.state;
const { inventoryManager } = this.props;
if (item) {
inventoryManager.handleCustomizationsRemove({ item: item.item });
}
};
handleAdd = (
item: Item,
amount: number,
containerDefinitionKey: string
): void => {
const { inventoryManager } = this.props;
inventoryManager.handleAdd(
{ item, amount, containerDefinitionKey },
AppNotificationUtils.handleItemAddAccepted.bind(this, item, amount),
AppNotificationUtils.handleItemAddRejected.bind(this, item, amount)
);
};
handleItemMove = (item: Item, containerDefinitionKey: string): void => {
const { inventoryManager } = this.props;
inventoryManager.handleMove({ item, containerDefinitionKey });
};
handleItemSetEquip = (item: Item, uses: number): void => {
const { inventoryManager } = this.props;
//TODO need different component than SlotManager for item equipped/unequipped
if (uses === 0) {
inventoryManager.handleUnequip({ item });
}
if (uses === 1) {
inventoryManager.handleEquip({ item });
}
};
handleDataOriginClick = (dataOrigin: DataOrigin) => {
const { paneHistoryPush } = this.props;
let component = getDataOriginComponentInfo(dataOrigin);
if (component.type !== PaneComponentEnum.ERROR_404) {
paneHistoryPush(component.type, component.identifiers);
}
};
handleSpellClick = (spell: Spell): void => {
const { paneHistoryPush } = this.props;
let component = getSpellComponentInfo(spell);
if (component.type) {
paneHistoryPush(component.type, component.identifiers);
}
};
handleInfusionClick = (infusion: Infusion): void => {
const { paneHistoryPush } = this.props;
const choiceKey = InfusionUtils.getChoiceKey(infusion);
if (choiceKey !== null) {
paneHistoryPush(
PaneComponentEnum.INFUSION_CHOICE,
PaneIdentifierUtils.generateInfusionChoice(choiceKey)
);
}
};
handleManageShow = (): void => {
const { paneHistoryPush } = this.props;
paneHistoryPush(PaneComponentEnum.EQUIPMENT_MANAGE);
};
handleStartingEquipmentShow = (): void => {
const { paneHistoryPush } = this.props;
paneHistoryPush(PaneComponentEnum.STARTING_EQUIPMENT);
};
handleCurrencyShow = (): void => {
const { paneHistoryPush } = this.props;
paneHistoryPush(PaneComponentEnum.CURRENCY);
};
handleEncumbranceShow = (): void => {
const { paneHistoryPush } = this.props;
paneHistoryPush(PaneComponentEnum.ENCUMBRANCE);
};
sortInventoryItems = (inventory: Array<Item>): Array<Item> => {
return orderBy(
inventory,
[
(item) => ItemUtils.getRarityLevel(item),
(item) => ItemUtils.getName(item),
(item) => ItemUtils.getMappingId(item),
],
["desc", "asc", "asc"]
);
};
handleOpenCustomize = () => {
this.setState({ isCustomizeClosed: !this.state.isCustomizeClosed });
};
hasCurrencyValueChanged = (
value: number,
currencyKey: keyof CharacterCurrencyContract,
coin: CharacterCurrencyContract
): boolean => {
return value !== coin[currencyKey];
};
handleCurrencyChangeError = (
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
)
);
}
};
handleCurrencyAdjust = (
coin: Partial<CharacterCurrencyContract>,
multiplier: 1 | -1,
containerDefinitionKey: string
): void => {
const { coinManager, dispatch } = this.props;
let actionLabel: string = "";
if (multiplier === 1) {
actionLabel = "Add";
} else if (multiplier === -1) {
actionLabel = "Delete";
}
coinManager.handleTransaction(
{ coin, containerDefinitionKey, multiplier },
() => {
// dispatch(toastActions.toastSuccess(
// `A ${containerDefinitionKey === coinManager.getPartyEquipmentContainerDefinitionKey() ? 'Party' : ''}Coin transaction was completed!`,
// 'could list the updates?'
// ));
},
() => {
dispatch(
toastActions.toastError(
`Unable to make transaction: ${actionLabel} Coin`,
"Cannot set currency to a negative value, the previous amount has been set instead."
)
);
}
);
};
handleAmountSet = (
containerDefinitionKey: string,
key: keyof CharacterCurrencyContract,
amount: number
): void => {
const { coinManager } = this.props;
const coin = coinManager.getContainerCoin(containerDefinitionKey);
if (coin && this.hasCurrencyValueChanged(amount, key, coin)) {
coinManager.handleAmountSet({
key,
amount,
containerDefinitionKey,
});
}
};
render() {
const { item, currentContainer, isCustomizeClosed } = this.state;
const {
weaponSpellDamageGroups,
ruleData,
snippetData,
entityValueLookup,
infusionChoiceLookup,
isReadonly,
proficiencyBonus,
theme,
containers,
globalModifiers,
valueLookupByType,
inventory,
identifiers,
partyInfo,
inventoryManager,
} = this.props;
if (currentContainer === null) {
return <PaneInitFailureContent />;
}
const shopOpenInitially = identifiers?.showAddItems ?? false;
const canCustomize = item
? inventoryManager.canCustomizeItem(item.item)
: false;
return (
<div className="ct-container-pane">
<Header
preview={
<Preview imageUrl={item ? item.getAvatarUrl() : undefined} />
}
>
{item ? (
<EditableName onClick={this.handleOpenCustomize}>
<ItemName item={item.item} />
</EditableName>
) : (
currentContainer.getName()
)}
</Header>
{item && (
<Collapsible
header="Item Description"
initiallyCollapsed={shopOpenInitially}
overrideCollapsed={shopOpenInitially}
className="ct-container-pane__content"
>
<ItemDetail
className="ct-container-pane__item-detail"
theme={theme}
key={item.getUniqueKey()}
item={item.item}
weaponSpellDamageGroups={weaponSpellDamageGroups}
ruleData={ruleData}
snippetData={snippetData}
onCustomDataUpdate={this.handleCustomDataUpdate}
onCustomizationsRemove={this.handleRemoveCustomizations}
onDataOriginClick={this.handleDataOriginClick}
onSpellClick={this.handleSpellClick}
onInfusionClick={this.handleInfusionClick}
entityValueLookup={entityValueLookup}
infusionChoiceLookup={infusionChoiceLookup}
isReadonly={isReadonly}
proficiencyBonus={proficiencyBonus}
showActions={false}
showCustomize={canCustomize}
container={currentContainer.container}
isCustomizeClosed={isCustomizeClosed}
onCustomizeClick={this.handleOpenCustomize}
/>
</Collapsible>
)}
<ContainerActions
currentContainer={currentContainer.container}
containers={containers}
ruleData={ruleData}
proficiencyBonus={proficiencyBonus}
theme={theme}
globalModifiers={globalModifiers}
valueLookupByType={valueLookupByType}
onItemAdd={this.handleAdd}
onItemMove={this.handleItemMove}
onItemEquip={this.handleItemSetEquip}
inventory={this.sortInventoryItems(
ContainerUtils.getInventoryItems(
currentContainer.container,
inventory
)
)}
shopOpenInitially={shopOpenInitially}
isReadonly={isReadonly}
partyInfo={partyInfo}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(this)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
{!isReadonly && item && <ItemDetailActions item={item.item} />}
{!isReadonly && currentContainer.isCharacterContainer() && (
<div className="ct-container-pane__links">
<div className="ct-container-pane__link">
<Link useTheme={true} onClick={this.handleManageShow}>
Manage Inventory
</Link>
</div>
<div className="ct-container-pane__link">
<Link useTheme={true} onClick={this.handleStartingEquipmentShow}>
Starting Equipment
</Link>
</div>
<div className="ct-container-pane__link">
<Link useTheme={true} onClick={this.handleCurrencyShow}>
Currency
</Link>
</div>
<div className="ct-container-pane__link">
<Link useTheme={true} onClick={this.handleEncumbranceShow}>
Encumbrance
</Link>
</div>
</div>
)}
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
ruleData: rulesEngineSelectors.getRuleData(state),
weaponSpellDamageGroups:
rulesEngineSelectors.getWeaponSpellDamageGroups(state),
entityValueLookup:
rulesEngineSelectors.getCharacterValueLookupByEntity(state),
snippetData: rulesEngineSelectors.getSnippetData(state),
infusionChoiceLookup: rulesEngineSelectors.getInfusionChoiceLookup(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
containers: rulesEngineSelectors.getInventoryContainers(state),
globalModifiers: rulesEngineSelectors.getValidGlobalModifiers(state),
valueLookupByType:
rulesEngineSelectors.getCharacterValueLookupByType(state),
inventory: rulesEngineSelectors.getAllInventoryItems(state),
containerLookup: rulesEngineSelectors.getContainerLookup(state),
partyInfo: serviceDataSelectors.getPartyInfo(state),
};
}
function ContainerPaneContainer(props) {
const { inventoryManager } = useContext(InventoryManagerContext);
const { coinManager } = useContext(CoinManagerContext);
const {
pane: { paneHistoryPush },
} = useSidebar();
return (
<ContainerPane
inventoryManager={inventoryManager}
coinManager={coinManager}
paneHistoryPush={paneHistoryPush}
{...props}
/>
);
}
export default connect(mapStateToProps)(ContainerPaneContainer);
@@ -0,0 +1,4 @@
import ContainerPane from "./ContainerPane";
export default ContainerPane;
export { ContainerPane };