2025-05-28 15:36:51 -07:00

245 lines
7.7 KiB
TypeScript

import React, { useContext } from "react";
import {
Collapsible,
CollapsibleHeaderContent,
CollapsibleHeading,
} from "@dndbeyond/character-components/es";
import {
CampaignUtils,
CharacterTheme,
Container,
ContainerUtils,
InventoryManager,
CharacterCurrencyContract,
Item,
ItemUtils,
PartyInfo,
RuleData,
CoinManager,
} from "@dndbeyond/character-rules-engine/es";
import { ItemName } from "~/components/ItemName";
import { NumberDisplay } from "~/components/NumberDisplay";
import CurrencyCollapsible from "../../../CharacterSheet/components/CurrencyCollapsible";
import { CurrencyErrorTypeEnum } from "../../containers/panes/CurrencyPane/CurrencyPaneConstants";
import { CoinManagerContext } from "../../managers/CoinManagerContext";
import { InventoryManagerContext } from "../../managers/InventoryManagerContext";
import { CustomItemCreator } from "../CustomItemCreator";
import EquipmentShop from "../EquipmentShop";
import ItemDetail from "../ItemDetail";
import { ItemSlotManager } from "../ItemSlotManager";
import { ThemeButtonWithMenu } from "../common/Button";
interface Props {
currentContainer: Container;
theme: CharacterTheme;
onItemMove?: (item: Item, containerDefinitionKey: string) => void;
onItemEquip?: (item: Item, uses: number) => void;
ruleData: RuleData;
proficiencyBonus: number;
inventory: Array<Item>;
containers: Array<Container>;
shopOpenInitially?: boolean;
isReadonly: boolean;
partyInfo: PartyInfo | null;
inventoryManager: InventoryManager;
coinManager: CoinManager;
handleCurrencyChangeError: (
currencyName: string,
errorType: CurrencyErrorTypeEnum
) => void;
handleCurrencyAdjust: (
coin: Partial<CharacterCurrencyContract>,
multiplier: 1 | -1,
containerDefinitionKey: string
) => void;
handleAmountSet: (
containerDefinitionKey: string,
key: keyof CharacterCurrencyContract,
amount: number
) => void;
}
export const ContainerActionsComponent: React.FC<Props> = ({
currentContainer,
theme,
ruleData,
onItemMove,
onItemEquip,
proficiencyBonus,
inventory,
containers,
shopOpenInitially = false,
isReadonly,
partyInfo,
inventoryManager,
coinManager,
handleCurrencyAdjust,
handleAmountSet,
handleCurrencyChangeError,
}) => {
let showEquipmentShop = true;
if (
ContainerUtils.isShared(currentContainer) &&
partyInfo &&
CampaignUtils.isSharingStateInactive(
CampaignUtils.getSharingState(partyInfo)
)
) {
showEquipmentShop = false;
}
let headerNode: React.ReactNode = (
<CollapsibleHeaderContent
heading={
<CollapsibleHeading>{`Contents (${inventory.length})`}</CollapsibleHeading>
}
callout={
<div className="ct-equipment-manage-pane__callout">
<NumberDisplay
type="weightInLb"
number={ContainerUtils.getWeightInfo(currentContainer).total}
/>
</div>
}
/>
);
return (
<div className="ct-container-manager">
{showEquipmentShop && (
<Collapsible
header="Add Items"
initiallyCollapsed={!shopOpenInitially}
overrideCollapsed={!shopOpenInitially}
>
<EquipmentShop
limitAddToCurrentContainer={currentContainer}
partyInfo={partyInfo}
containers={containers}
theme={theme}
ruleData={ruleData}
proficiencyBonus={proficiencyBonus}
/>
<CustomItemCreator containers={[currentContainer]} />
</Collapsible>
)}
{coinManager.canUseCointainers() && (
<CurrencyCollapsible
heading={`${ContainerUtils.getName(currentContainer)} Coin`}
initiallyCollapsed={true}
isReadonly={isReadonly}
container={currentContainer}
handleCurrencyChangeError={handleCurrencyChangeError}
handleCurrencyAdjust={handleCurrencyAdjust}
handleAmountSet={handleAmountSet}
/>
)}
<Collapsible
header={headerNode}
initiallyCollapsed={shopOpenInitially}
overrideCollapsed={shopOpenInitially}
>
<div className="ct-container-manager__inventory">
{inventory.map((item, idx) => {
const canEquip = inventoryManager.canEquipUnequipItem(item);
const key = ItemUtils.getUniqueKey(item);
return (
<Collapsible
key={`${key}-${idx}`}
className="ct-container-manager__item"
layoutType={"minimal"}
header={
<CollapsibleHeaderContent
heading={
<div className="ct-container-manager__item-header">
<div className="ct-container-manager__item-header-action">
<ItemSlotManager
isUsed={!!ItemUtils.isEquipped(item)}
isReadonly={isReadonly}
canUse={canEquip}
onSet={(uses) => {
if (onItemEquip) {
onItemEquip(item, uses);
}
}}
theme={theme}
useTooltip={false}
/>
</div>
<div className="ct-container-manager__item-header-name">
<ItemName item={item} showLegacy={true} />
</div>
</div>
}
callout={
!isReadonly &&
partyInfo &&
inventoryManager.canMoveItem(item) ? (
<ThemeButtonWithMenu
showSingleOption={true}
containerEl={
document.querySelector(
".ct-sidebar__portal"
) as HTMLElement
}
groupedOptions={ContainerUtils.getGroupedOptions(
ItemUtils.getContainerDefinitionKey(item),
containers,
"Move To:",
CampaignUtils.getSharingState(partyInfo)
)}
buttonStyle="outline"
onSelect={(definitionKey) => {
if (onItemMove) {
onItemMove(item, definitionKey);
}
}}
>
Move
</ThemeButtonWithMenu>
) : null
}
/>
}
>
<ItemDetail
theme={theme}
item={item}
ruleData={ruleData}
showCustomize={false}
showImage={false}
proficiencyBonus={proficiencyBonus}
/>
</Collapsible>
);
})}
{ContainerUtils.getItemMappingIds(currentContainer).length === 0 && (
<div className="ct-container-manager__empty">
There are no items in your{" "}
{ContainerUtils.getName(currentContainer)}
</div>
)}
</div>
</Collapsible>
</div>
);
};
export const ContainerActions = (props) => {
const { inventoryManager } = useContext(InventoryManagerContext);
const { coinManager } = useContext(CoinManagerContext);
return (
<ContainerActionsComponent
inventoryManager={inventoryManager}
coinManager={coinManager}
{...props}
/>
);
};
export default ContainerActions;