``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
451 lines
12 KiB
TypeScript
451 lines
12 KiB
TypeScript
import React from "react";
|
|
import { useContext } from "react";
|
|
import { connect, DispatchProp } from "react-redux";
|
|
|
|
import {
|
|
rulesEngineSelectors,
|
|
CampaignUtils,
|
|
serviceDataActions,
|
|
Constants,
|
|
Container,
|
|
ContainerUtils,
|
|
InventoryManager,
|
|
InfusionUtils,
|
|
Item,
|
|
ItemUtils,
|
|
RuleData,
|
|
GroupedMenuOption,
|
|
serviceDataSelectors,
|
|
PartyInfo,
|
|
} from "@dndbeyond/character-rules-engine/es";
|
|
|
|
import { Button } from "~/components/Button";
|
|
import { Popover } from "~/components/Popover";
|
|
import { PopoverContent } from "~/components/PopoverContent";
|
|
import { useSidebar } from "~/contexts/Sidebar";
|
|
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
|
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 {
|
|
item: Item;
|
|
hasMaxAttunedItems: boolean;
|
|
ruleData: RuleData;
|
|
isReadonly: boolean;
|
|
containers: Array<Container>;
|
|
onPostRemoveNavigation?: PaneComponentEnum;
|
|
partyInfo: PartyInfo | null;
|
|
inventoryManager: InventoryManager;
|
|
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
|
}
|
|
interface State {
|
|
quantity: number;
|
|
newQuantity: number;
|
|
}
|
|
class ItemDetailActions extends React.PureComponent<Props, State> {
|
|
static defaultProps = {
|
|
hasMaxAttunedItems: false,
|
|
};
|
|
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
quantity: ItemUtils.getQuantity(props.item),
|
|
newQuantity: ItemUtils.getQuantity(props.item),
|
|
};
|
|
}
|
|
|
|
componentDidUpdate(
|
|
prevProps: Readonly<Props>,
|
|
prevState: Readonly<State>,
|
|
snapshot?: any
|
|
): void {
|
|
const { item } = this.props;
|
|
|
|
const nextQuantity = ItemUtils.getQuantity(item);
|
|
if (nextQuantity !== prevState.quantity) {
|
|
this.setState({
|
|
quantity: nextQuantity,
|
|
newQuantity: nextQuantity,
|
|
});
|
|
}
|
|
}
|
|
|
|
handleQuantityUpdate = (newValue: number): void => {
|
|
const { item, inventoryManager } = this.props;
|
|
|
|
inventoryManager.handleQuantitySet({ item, quantity: newValue });
|
|
};
|
|
|
|
handleRemove = (): void => {
|
|
const { paneHistoryStart, item, onPostRemoveNavigation, inventoryManager } =
|
|
this.props;
|
|
|
|
if (onPostRemoveNavigation) {
|
|
paneHistoryStart(onPostRemoveNavigation);
|
|
}
|
|
|
|
inventoryManager.handleRemove({ item });
|
|
|
|
if (ItemUtils.isContainer(item)) {
|
|
paneHistoryStart(PaneComponentEnum.EQUIPMENT_MANAGE);
|
|
}
|
|
};
|
|
|
|
handleRemoveInfusion = (): void => {
|
|
const { dispatch, paneHistoryStart, item } = this.props;
|
|
|
|
const infusion = ItemUtils.getInfusion(item);
|
|
if (infusion) {
|
|
const infusionId = InfusionUtils.getId(infusion);
|
|
if (infusionId === null) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
InfusionUtils.getType(infusion) === Constants.InfusionTypeEnum.REPLICATE
|
|
) {
|
|
const choiceKey = InfusionUtils.getChoiceKey(infusion);
|
|
if (choiceKey !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.INFUSION_CHOICE,
|
|
PaneIdentifierUtils.generateInfusionChoice(choiceKey)
|
|
);
|
|
}
|
|
}
|
|
|
|
dispatch(
|
|
serviceDataActions.infusionMappingDestroy(
|
|
infusionId,
|
|
InfusionUtils.getInventoryMappingId(infusion)
|
|
)
|
|
);
|
|
}
|
|
};
|
|
|
|
handleRemoveInfusions = (itemIds: Array<number>): void => {
|
|
const { dispatch } = this.props;
|
|
|
|
itemIds.forEach((id) => {
|
|
dispatch(serviceDataActions.infusionMappingsDestroy(itemIds));
|
|
});
|
|
};
|
|
|
|
handleUnequip = (): void => {
|
|
const { inventoryManager, item } = this.props;
|
|
inventoryManager.handleUnequip({ item });
|
|
};
|
|
|
|
handleEquip = (): void => {
|
|
const { inventoryManager, item } = this.props;
|
|
inventoryManager.handleEquip({ item });
|
|
};
|
|
|
|
handleAttune = (): void => {
|
|
const { inventoryManager, item } = this.props;
|
|
|
|
inventoryManager.handleEquip({ item });
|
|
inventoryManager.handleAttune({ item });
|
|
};
|
|
|
|
handleUnattune = (): void => {
|
|
const { inventoryManager, item } = this.props;
|
|
|
|
inventoryManager.handleUnattune({ item });
|
|
};
|
|
|
|
handleMove = (containerDefinitionKey: string): void => {
|
|
const { inventoryManager, item } = this.props;
|
|
|
|
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;
|
|
|
|
return ContainerUtils.getGroupedOptions(
|
|
ItemUtils.getContainerDefinitionKey(item),
|
|
containers,
|
|
"Move To:",
|
|
partyInfo
|
|
? CampaignUtils.getSharingState(partyInfo)
|
|
: Constants.PartyInventorySharingStateEnum.OFF
|
|
);
|
|
};
|
|
|
|
renderRemove = (): React.ReactNode => {
|
|
const { item, containers, isReadonly, inventoryManager } = this.props;
|
|
|
|
const canRemoveItem = inventoryManager.canRemoveItem(item);
|
|
if (isReadonly || !canRemoveItem) {
|
|
return null;
|
|
}
|
|
|
|
const infusion = ItemUtils.getInfusion(item);
|
|
|
|
const name = ItemUtils.getName(item);
|
|
const isContainer = ItemUtils.isContainer(item);
|
|
|
|
if (isContainer) {
|
|
const container = containers.find((container) => {
|
|
return (
|
|
ContainerUtils.getContainerType(container) ===
|
|
Constants.ContainerTypeEnum.ITEM &&
|
|
ContainerUtils.getMappingId(container) ===
|
|
ItemUtils.getMappingId(item)
|
|
);
|
|
});
|
|
|
|
if (container) {
|
|
if (infusion) {
|
|
return (
|
|
<RemoveButton
|
|
onClick={this.handleRemove}
|
|
className="ct-item-detail__action"
|
|
data-testid="remove-infusion-button"
|
|
>
|
|
Remove Infusion
|
|
</RemoveButton>
|
|
);
|
|
}
|
|
const hasInfusions = ContainerUtils.hasInfusions(container);
|
|
|
|
return (
|
|
<Popover
|
|
trigger={
|
|
<Button size="xx-small" variant="outline" themed>
|
|
Delete
|
|
</Button>
|
|
}
|
|
position="bottom"
|
|
data-testid="delete-item-button"
|
|
maxWidth={250}
|
|
>
|
|
<PopoverContent
|
|
title={`Remove ${name}?`}
|
|
content={`Removing the ${name} will also remove all of its ${
|
|
hasInfusions ? "infusions and " : " "
|
|
} contents.`}
|
|
confirmText="Delete"
|
|
onConfirm={() => this.handleRemove()}
|
|
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
|
|
onClick={this.handleRemoveInfusion}
|
|
className="ct-item-detail__action"
|
|
data-testid="remove-infusion-button"
|
|
>
|
|
Remove Infusion
|
|
</RemoveButton>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<RemoveButton
|
|
onClick={this.handleRemove}
|
|
className="ct-item-detail__action"
|
|
data-testid="delete-item-button"
|
|
>
|
|
Delete
|
|
</RemoveButton>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
item,
|
|
hasMaxAttunedItems,
|
|
isReadonly,
|
|
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)
|
|
);
|
|
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;
|
|
|
|
return (
|
|
<div className="ct-item-detail__actions">
|
|
{isStackable && (
|
|
<SimpleQuantity
|
|
quantity={quantity}
|
|
onUpdate={this.handleQuantityUpdate}
|
|
isReadonly={isReadonly || !canModifyQuantity}
|
|
/>
|
|
)}
|
|
|
|
{canEquip && !isReadonly && (
|
|
<ThemeButton
|
|
onClick={isEquipped ? this.handleUnequip : this.handleEquip}
|
|
style={isEquipped ? "" : "outline"}
|
|
size="small"
|
|
className="ct-item-detail__action"
|
|
data-testid="equip-item-button"
|
|
>
|
|
{isEquipped ? labels.unequip : labels.equip}
|
|
</ThemeButton>
|
|
)}
|
|
{canAttune && !isReadonly && (
|
|
<ThemeButton
|
|
onClick={isAttuned ? this.handleUnattune : this.handleAttune}
|
|
style={isAttuned ? "" : "outline"}
|
|
size="small"
|
|
disabled={hasMaxAttunedItems && !isAttuned}
|
|
className="ct-item-detail__action"
|
|
data-testid="attune-item-button"
|
|
>
|
|
{isAttuned ? labels.unattune : labels.attune}
|
|
</ThemeButton>
|
|
)}
|
|
{canShareItem && (
|
|
<ThemeButton
|
|
style="outline"
|
|
onClick={() => {
|
|
const definitionKey =
|
|
inventoryManager.getPartyEquipmentContainerDefinitionKey();
|
|
if (definitionKey) {
|
|
this.handleMove(definitionKey);
|
|
}
|
|
}}
|
|
size="small"
|
|
className="ct-item-detail__action"
|
|
data-testid="move-item-button"
|
|
>
|
|
Move
|
|
</ThemeButton>
|
|
)}
|
|
{canClaimItem && (
|
|
<ThemeButton
|
|
style="outline"
|
|
onClick={() => {
|
|
const definitionKey =
|
|
inventoryManager.getCharacterContainerDefinitionKey();
|
|
if (definitionKey) {
|
|
this.handleMove(definitionKey);
|
|
}
|
|
}}
|
|
size="small"
|
|
className="ct-item-detail__action"
|
|
data-testid="move-item-button"
|
|
>
|
|
Move
|
|
</ThemeButton>
|
|
)}
|
|
{canMoveItem && (
|
|
<ThemeButtonWithMenu
|
|
buttonStyle="outline"
|
|
containerEl={
|
|
document.querySelector(".ct-sidebar__portal") as HTMLElement
|
|
}
|
|
className="ct-item-detail__action"
|
|
groupedOptions={containerMoveOptions}
|
|
showSingleOption={true}
|
|
onSelect={this.handleMove}
|
|
>
|
|
Move
|
|
</ThemeButtonWithMenu>
|
|
)}
|
|
{this.renderRemove()}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
function mapStateToProps(state: SharedAppState) {
|
|
return {
|
|
ruleData: rulesEngineSelectors.getRuleData(state),
|
|
isReadonly: appEnvSelectors.getIsReadonly(state),
|
|
hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
|
|
containers: rulesEngineSelectors.getInventoryContainers(state),
|
|
partyInfo: serviceDataSelectors.getPartyInfo(state),
|
|
};
|
|
}
|
|
|
|
function ItemDetailActionsContainer(props) {
|
|
const { inventoryManager } = useContext(InventoryManagerContext);
|
|
const {
|
|
pane: { paneHistoryStart },
|
|
} = useSidebar();
|
|
return (
|
|
<ItemDetailActions
|
|
inventoryManager={inventoryManager}
|
|
paneHistoryStart={paneHistoryStart}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default connect(mapStateToProps)(ItemDetailActionsContainer);
|