New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
@@ -1,10 +1,10 @@
import React from "react";
import { useContext } from "react";
import { connect, DispatchProp } from "react-redux";
import { AnyAction } from "redux";
import {
rulesEngineSelectors,
CampaignUtils,
serviceDataActions,
Constants,
Container,
@@ -17,6 +17,8 @@ import {
GroupedMenuOption,
serviceDataSelectors,
PartyInfo,
ItemPlan,
ContainerLookup,
} from "@dndbeyond/character-rules-engine/es";
import { Button } from "~/components/Button";
@@ -24,29 +26,18 @@ import { Popover } from "~/components/Popover";
import { PopoverContent } from "~/components/PopoverContent";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { ButtonWithMenu } from "~/subApps/sheet/components/Sidebar/components/ButtonWithMenu";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import SimpleQuantity from "../../components/SimpleQuantity";
import {
RemoveButton,
ThemeButton,
ThemeButtonWithMenu,
} from "../../components/common/Button";
import { InventoryManagerContext } from "../../managers/InventoryManagerContext";
import * as appEnvSelectors from "../../selectors/appEnv";
import { SharedAppState } from "../../stores/typings";
import { PaneIdentifierUtils } from "../../utils";
interface LabelLookup {
consume: string;
remove: string;
unequip: string;
equip: string;
attune: string;
unattune: string;
}
interface Props extends DispatchProp {
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
interface Props extends DispatchProp<AnyAction> {
item: Item;
hasMaxAttunedItems: boolean;
ruleData: RuleData;
@@ -56,6 +47,10 @@ interface Props extends DispatchProp {
partyInfo: PartyInfo | null;
inventoryManager: InventoryManager;
paneHistoryStart: PaneInfo["paneHistoryStart"];
itemPlans: Array<ItemPlan>;
containerLookup: ContainerLookup;
isUserDM: boolean;
onRemoveItem?: () => void;
}
interface State {
quantity: number;
@@ -98,8 +93,17 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
};
handleRemove = (): void => {
const { paneHistoryStart, item, onPostRemoveNavigation, inventoryManager } =
this.props;
const {
paneHistoryStart,
item,
onPostRemoveNavigation,
inventoryManager,
onRemoveItem,
} = this.props;
if (onRemoveItem) {
onRemoveItem();
}
if (onPostRemoveNavigation) {
paneHistoryStart(onPostRemoveNavigation);
@@ -113,7 +117,13 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
};
handleRemoveInfusion = (): void => {
const { dispatch, paneHistoryStart, item } = this.props;
const {
dispatch,
paneHistoryStart,
item,
inventoryManager,
containerLookup,
} = this.props;
const infusion = ItemUtils.getInfusion(item);
if (infusion) {
@@ -137,7 +147,10 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
dispatch(
serviceDataActions.infusionMappingDestroy(
infusionId,
InfusionUtils.getInventoryMappingId(infusion)
InfusionUtils.getInventoryMappingId(infusion),
inventoryManager.handleAcceptOnSuccess(
ItemUtils.isShared(item, containerLookup)
)
)
);
}
@@ -180,34 +193,30 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
inventoryManager.handleMove({ item, containerDefinitionKey });
};
getLabels = (): LabelLookup => {
return {
consume: "Consume",
remove: "Delete Item",
unequip: "Unequip",
equip: "Equip",
attune: "Attune",
unattune: "Unattune",
};
};
getContainerOptions = (): Array<GroupedMenuOption> => {
const { containers, item, partyInfo } = this.props;
const { containers, item } = this.props;
return ContainerUtils.getGroupedOptions(
ItemUtils.getContainerDefinitionKey(item),
containers,
"Move To:",
partyInfo
? CampaignUtils.getSharingState(partyInfo)
: Constants.PartyInventorySharingStateEnum.OFF
"Move To:"
);
};
renderRemove = (): React.ReactNode => {
const { item, containers, isReadonly, inventoryManager } = this.props;
const {
item,
containers,
isReadonly,
inventoryManager,
itemPlans,
isUserDM,
} = this.props;
const canRemoveItem = isUserDM
? true
: inventoryManager.canRemoveItem(item);
const canRemoveItem = inventoryManager.canRemoveItem(item);
if (isReadonly || !canRemoveItem) {
return null;
}
@@ -216,6 +225,7 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
const name = ItemUtils.getName(item);
const isContainer = ItemUtils.isContainer(item);
const itemPlan = ItemUtils.getItemPlan(item, itemPlans);
if (isContainer) {
const container = containers.find((container) => {
@@ -230,13 +240,15 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
if (container) {
if (infusion) {
return (
<RemoveButton
<Button
onClick={this.handleRemove}
className="ct-item-detail__action"
variant="outline"
size="xx-small"
themed
data-testid="remove-infusion-button"
>
Remove Infusion
</RemoveButton>
</Button>
);
}
const hasInfusions = ContainerUtils.hasInfusions(container);
@@ -245,7 +257,7 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
<Popover
trigger={
<Button size="xx-small" variant="outline" themed>
Delete
Delete{itemPlan ? " Replicated Item" : ""}
</Button>
}
position="bottom"
@@ -262,43 +274,34 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
withCancel
/>
</Popover>
// <RemoveButtonWithModal
// className="ct-item-detail__action"
// style={"outline"}
// size="small"
// modalTitle={`Delete ${name}?`}
// modalContent={`Deleting the ${name} will also delete all of its${
// hasInfusions ? " infusions and" : ""
// } contents.`}
// confirmCallback={() => {
// this.handleRemove();
// }}
// data-testid="delete-item-button"
// />
);
}
}
if (infusion) {
return (
<RemoveButton
<Button
onClick={this.handleRemoveInfusion}
className="ct-item-detail__action"
variant="outline"
size="xx-small"
themed
data-testid="remove-infusion-button"
>
Remove Infusion
</RemoveButton>
</Button>
);
}
return (
<RemoveButton
<Button
onClick={this.handleRemove}
className="ct-item-detail__action"
variant="outline"
size="xx-small"
themed
data-testid="delete-item-button"
>
Delete
</RemoveButton>
Delete{itemPlan ? " Replicated Item" : ""}
</Button>
);
};
@@ -310,14 +313,11 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
containers,
inventoryManager,
} = this.props;
const labels = this.getLabels();
const containerMoveOptions = this.getContainerOptions();
const isEquipped = ItemUtils.isEquipped(item);
const isAttuned = ItemUtils.isAttuned(item);
const isStackable = ItemUtils.isStackable(item);
const quantity = ItemUtils.getQuantity(item);
const isContainer = ItemUtils.getDefinitionIsContainer(item);
const parentContainer = ContainerUtils.getItemParentContainer(
containers,
ItemUtils.getContainerDefinitionKey(item)
@@ -325,16 +325,20 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
if (!parentContainer) {
return null;
}
const canEquip = inventoryManager.canEquipUnequipItem(item);
const canAttune = inventoryManager.canAttuneUnattuneItem(item);
const canModifyQuantity = inventoryManager.canModifyQuantity(item);
const canMoveItem =
inventoryManager.canMoveItem(item) &&
!isContainer &&
!isReadonly &&
!!containers.length;
const canShareItem = inventoryManager.canShareItem(item) && !isReadonly;
const canClaimItem = inventoryManager.canClaimItem(item) && !isReadonly;
const canEquip = inventoryManager.canEquipUnequipItem(item) && !isReadonly;
const canAttune =
inventoryManager.canAttuneUnattuneItem(item) && !isReadonly;
const canModifyQuantity =
inventoryManager.canModifyQuantity(item) && !isReadonly;
const canMoveItem = inventoryManager.canMoveItem(item) && !isReadonly;
const canShareContainer =
inventoryManager.canShareContainer(item) && !isReadonly;
const canClaimContainer =
inventoryManager.canClaimContainer(item) && !isReadonly;
const partyInventoryDefinitionKey =
inventoryManager.getPartyEquipmentContainerDefinitionKey();
const characterInventoryDefinitionKey =
inventoryManager.getCharacterContainerDefinitionKey();
return (
<div className="ct-item-detail__actions">
@@ -342,80 +346,86 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
<SimpleQuantity
quantity={quantity}
onUpdate={this.handleQuantityUpdate}
isReadonly={isReadonly || !canModifyQuantity}
isReadonly={!canModifyQuantity}
/>
)}
{canEquip && !isReadonly && (
<ThemeButton
{canEquip && (
<Button
onClick={isEquipped ? this.handleUnequip : this.handleEquip}
style={isEquipped ? "" : "outline"}
size="small"
className="ct-item-detail__action"
variant={isEquipped ? "solid" : "outline"}
size="xx-small"
themed
data-testid="equip-item-button"
>
{isEquipped ? labels.unequip : labels.equip}
</ThemeButton>
{isEquipped ? "Unequip" : "Equip"}
</Button>
)}
{canAttune && !isReadonly && (
<ThemeButton
{canAttune && (
<Button
onClick={isAttuned ? this.handleUnattune : this.handleAttune}
style={isAttuned ? "" : "outline"}
size="small"
variant={isAttuned ? "solid" : "outline"}
size="xx-small"
themed
disabled={hasMaxAttunedItems && !isAttuned}
className="ct-item-detail__action"
data-testid="attune-item-button"
>
{isAttuned ? labels.unattune : labels.attune}
</ThemeButton>
{isAttuned ? "Unattune" : "Attune"}
</Button>
)}
{canShareItem && (
<ThemeButton
style="outline"
onClick={() => {
const definitionKey =
inventoryManager.getPartyEquipmentContainerDefinitionKey();
if (definitionKey) {
this.handleMove(definitionKey);
}
}}
size="small"
className="ct-item-detail__action"
{canShareContainer && partyInventoryDefinitionKey && (
<ButtonWithMenu
showSingleOption={true}
placement="bottom-end"
variant="outline"
groupedOptions={[
{
label: "Move To:",
options: [
{
label: "Party Inventory",
value: partyInventoryDefinitionKey,
},
],
},
]}
onSelect={() => this.handleMove(partyInventoryDefinitionKey)}
data-testid="move-item-button"
>
Move
</ThemeButton>
</ButtonWithMenu>
)}
{canClaimItem && (
<ThemeButton
style="outline"
onClick={() => {
const definitionKey =
inventoryManager.getCharacterContainerDefinitionKey();
if (definitionKey) {
this.handleMove(definitionKey);
}
}}
size="small"
className="ct-item-detail__action"
{canClaimContainer && characterInventoryDefinitionKey && (
<ButtonWithMenu
showSingleOption={true}
placement="bottom-end"
variant="outline"
groupedOptions={[
{
label: "Move To:",
options: [
{
label: "My Inventory",
value: characterInventoryDefinitionKey,
},
],
},
]}
onSelect={() => this.handleMove(characterInventoryDefinitionKey)}
data-testid="move-item-button"
>
Move
</ThemeButton>
</ButtonWithMenu>
)}
{canMoveItem && (
<ThemeButtonWithMenu
buttonStyle="outline"
containerEl={
document.querySelector(".ct-sidebar__portal") as HTMLElement
}
className="ct-item-detail__action"
<ButtonWithMenu
variant="outline"
groupedOptions={containerMoveOptions}
showSingleOption={true}
onSelect={this.handleMove}
>
Move
</ThemeButtonWithMenu>
</ButtonWithMenu>
)}
{this.renderRemove()}
</div>
@@ -430,6 +440,9 @@ function mapStateToProps(state: SharedAppState) {
hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
containers: rulesEngineSelectors.getInventoryContainers(state),
partyInfo: serviceDataSelectors.getPartyInfo(state),
itemPlans: rulesEngineSelectors.getAvailableItemPlans(state),
containerLookup: rulesEngineSelectors.getContainerLookup(state),
isUserDM: appEnvSelectors.getIsUserDM(state),
};
}
@@ -438,6 +451,7 @@ function ItemDetailActionsContainer(props) {
const {
pane: { paneHistoryStart },
} = useSidebar();
return (
<ItemDetailActions
inventoryManager={inventoryManager}