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
@@ -29,14 +29,8 @@ export const ArmorListItem: FC<ArmorListItemProps> = ({
...props
}) => {
const actions = useEquipmentMethods();
const {
ruleData,
proficiencyBonus,
characterTheme,
itemUtils,
helperUtils,
containerLookup,
} = useCharacterEngine();
const { ruleData, characterTheme, itemUtils, helperUtils, containerLookup } =
useCharacterEngine();
const image = itemUtils.getAvatarUrl(item);
const altText = itemUtils.getName(item);
const container = helperUtils.lookupDataOrFallback(
@@ -74,7 +68,6 @@ export const ArmorListItem: FC<ArmorListItemProps> = ({
ruleData={ruleData}
showCustomize={false}
showActions={false}
proficiencyBonus={proficiencyBonus}
/>
<EquipmentListItemActions item={item} {...actions} {...props} />
</Accordion>
@@ -7,87 +7,6 @@ import {
ItemUtils,
} from "@dndbeyond/character-rules-engine/es";
import Collapsible, { CollapsibleHeader } from "../common/Collapsible";
export class EquipmentListItemHeaderAction extends React.PureComponent<{
item: Item;
heading: any;
metaItems: Array<string>;
imageUrl: string;
onUnequip?: (item: Item) => void;
onEquip?: (item: Item) => void;
unequipLabel: string;
equipLabel: string;
}> {
static defaultProps = {
showHeaderAction: false,
consumeLabel: "Consume",
};
handleUnequip = () => {
const { onUnequip, item } = this.props;
if (onUnequip) {
onUnequip(item);
}
};
handleEquip = () => {
const { onEquip, item } = this.props;
if (onEquip) {
onEquip(item);
}
};
render() {
const { item, heading, imageUrl, metaItems, unequipLabel, equipLabel } =
this.props;
const isEquipped = ItemUtils.isEquipped(item);
const isStackable = ItemUtils.isStackable(item);
const canEquip = ItemUtils.canEquip(item);
const quantity = ItemUtils.getQuantity(item);
const callout = (
<div className="equipment-list-item-callout">
{isStackable && (
<div className="equipment-list-item-callout-quantity">
<span className="equipment-list-item-callout-quantity-extra">
Qty
</span>
<span className="equipment-list-item-callout-quantity-value">
{quantity}
</span>
</div>
)}
{canEquip && (
<div className="equipment-list-item-callout-action">
<Button
onClick={isEquipped ? this.handleUnequip : this.handleEquip}
style={isEquipped ? "" : "outline"}
size="small"
>
{isEquipped ? unequipLabel : equipLabel}
</Button>
</div>
)}
</div>
);
return (
<CollapsibleHeader
imgSrc={imageUrl}
heading={heading}
metaItems={metaItems}
callout={callout}
/>
);
}
}
interface EquipmentListItemProps {
item: Item;
@@ -241,7 +160,7 @@ export class EquipmentListItemActions extends React.PureComponent<
handleAmountBlur = (evt) => {
const parsedValue = HelperUtils.parseInputInt(evt.target.value);
const clampedValue = HelperUtils.clampInt(parsedValue ? parsedValue : 0, 0);
const clampedValue = HelperUtils.clampInt(parsedValue ?? 0, 0);
this.handleQuantitySave(clampedValue);
};
@@ -394,25 +313,3 @@ export class EquipmentListItemActions extends React.PureComponent<
);
}
}
export default class EquipmentListItem extends React.PureComponent<{
header: any;
clsNames: Array<string>;
}> {
static defaultProps = {
clsNames: [],
};
render() {
let { header, children, clsNames } = this.props;
return (
<Collapsible
trigger={header}
clsNames={["equipment-list-item", ...clsNames]}
>
{children}
</Collapsible>
);
}
}
@@ -1,11 +0,0 @@
import EquipmentListItem, {
EquipmentListItemActions,
EquipmentListItemHeaderAction,
} from "./EquipmentListItem";
export default EquipmentListItem;
export {
EquipmentListItem,
EquipmentListItemActions,
EquipmentListItemHeaderAction,
};
@@ -3,7 +3,6 @@ import React from "react";
import { LoadingPlaceholder, Button } from "@dndbeyond/character-components/es";
import {
BaseItemDefinitionContract,
HelperUtils,
Item,
ItemUtils,
@@ -17,14 +16,14 @@ import {
ItemManager,
} from "@dndbeyond/character-rules-engine/es";
import { Accordion } from "~/components/Accordion";
import { AccordionSummary } from "~/components/AccordionSummary";
import { ItemFilter } from "~/components/ItemFilter";
import { ItemName } from "~/components/ItemName";
import { LegacyBadge } from "~/components/LegacyBadge";
import { AppLoggerUtils } from "../../../utils";
import ItemDetail from "../../ItemDetail";
import EquipmentListItem from "../EquipmentListItem";
import { CollapsibleHeader, CollapsibleHeading } from "../common/Collapsible";
import styles from "./styles.module.css";
//TODO: This component should be removed and replaced with the EquipmentShop component
export class EquipmentManagerShopItem extends React.PureComponent<
@@ -38,7 +37,6 @@ export class EquipmentManagerShopItem extends React.PureComponent<
minAddAmount: number;
maxAddAmount: number;
ruleData: RuleData;
proficiencyBonus: number;
containerDefinitionKey: string;
theme: CharacterTheme;
},
@@ -113,80 +111,28 @@ export class EquipmentManagerShopItem extends React.PureComponent<
);
};
renderButtons = () => {
const { item } = this.props;
return (
<div className="equipment-list-header-actions">
<Button size="small" onClick={this.handleAdd}>
Add
</Button>
</div>
);
};
renderHeader(item, metaItems, defaultImageUrl) {
const { theme } = this.props;
const { canAttune, avatarUrl } = item.definition;
const isLegacy = ItemUtils.isLegacy(item);
const heading = (
<CollapsibleHeading>
<div className="equipment-list-heading">
<span className="equipment-list-heading-text">
<ItemName item={item} />
</span>
<span className="equipment-list-heading-icons">
{canAttune ? (
<i className="equipment-list-heading-icon i-req-attunement" />
) : (
""
)}
</span>
{isLegacy && <LegacyBadge variant="margin-left" />}
</div>
</CollapsibleHeading>
);
let imageUrl = avatarUrl;
if (!imageUrl) {
imageUrl = defaultImageUrl;
}
return (
<CollapsibleHeader
imgSrc={imageUrl ? imageUrl : ""}
heading={heading}
metaItems={metaItems}
callout={this.renderButtons()}
/>
);
}
render() {
const { amount } = this.state;
const {
item,
minAddAmount,
maxAddAmount,
ruleData,
proficiencyBonus,
theme,
} = this.props;
const { item, minAddAmount, maxAddAmount, ruleData, theme } = this.props;
const avatarUrl = ItemUtils.getDefinitionAvatarUrl(item);
let metaItems = [ItemUtils.getType(item)];
let defaultImageUrl: string | null = null;
// Set default avatar for weapon
if (ItemUtils.isWeaponContract(item)) {
defaultImageUrl = RuleDataUtils.getDefaultWeaponImageUrl(ruleData);
} else if (ItemUtils.isGearContract(item)) {
}
// Set default avatar for gear
else if (ItemUtils.isGearContract(item)) {
const subType = ItemUtils.getSubType(item);
if (subType) {
metaItems.push(subType);
}
defaultImageUrl = RuleDataUtils.getDefaultGearImageUrl(ruleData);
} else if (ItemUtils.isArmorContract(item)) {
}
// Set default avatar for armor
else if (ItemUtils.isArmorContract(item)) {
const baseArmorName = ItemUtils.getBaseArmorName(item);
if (baseArmorName) {
metaItems.push(baseArmorName);
@@ -199,8 +145,22 @@ export class EquipmentManagerShopItem extends React.PureComponent<
(amount === null ? 1 : amount) * ItemUtils.getBundleSize(item);
return (
<EquipmentListItem
header={this.renderHeader(item, metaItems, defaultImageUrl)}
<Accordion
className={styles.accordion}
variant="paper"
summary={
<AccordionSummary
className={styles.accordionSummary}
title={<ItemName item={item} showLegacyBadge={true} />}
description={metaItems as string[]}
imageUrl={avatarUrl ?? defaultImageUrl ?? undefined}
callout={
<Button size="small" onClick={this.handleAdd}>
Add
</Button>
}
/>
}
>
<ItemDetail
theme={theme}
@@ -208,7 +168,6 @@ export class EquipmentManagerShopItem extends React.PureComponent<
ruleData={ruleData}
showCustomize={false}
showActions={false}
proficiencyBonus={proficiencyBonus}
/>
<div className="equipment-list-item-actions">
<div className="equipment-list-item-action">
@@ -251,7 +210,7 @@ export class EquipmentManagerShopItem extends React.PureComponent<
</Button>
</div>
</div>
</EquipmentListItem>
</Accordion>
);
}
}
@@ -266,7 +225,6 @@ interface EquipmentManagerShopProps {
containerDefinitionKey: string
) => void;
ruleData: RuleData;
proficiencyBonus: number;
containerDefinitionKey: string;
theme: CharacterTheme;
activeSourceCategories: Array<number>;
@@ -465,14 +423,8 @@ export default class EquipmentManagerShop extends React.PureComponent<
renderPagedListing = (filteredItems: Array<ItemManager>): React.ReactNode => {
const { currentPage } = this.state;
const {
pageSize,
onItemAdd,
ruleData,
proficiencyBonus,
containerDefinitionKey,
theme,
} = this.props;
const { pageSize, onItemAdd, ruleData, containerDefinitionKey, theme } =
this.props;
const pagedFilteredItems: Array<ItemManager> = filteredItems.slice(
0,
@@ -489,7 +441,6 @@ export default class EquipmentManagerShop extends React.PureComponent<
key={`${item.getMappingId()}-${idx}`}
onItemAdd={onItemAdd}
ruleData={ruleData}
proficiencyBonus={proficiencyBonus}
containerDefinitionKey={containerDefinitionKey}
/>
))
@@ -28,14 +28,8 @@ export const GearListItem: FC<GearListProps> = ({
unequipLabel,
...props
}) => {
const {
ruleData,
proficiencyBonus,
characterTheme,
itemUtils,
helperUtils,
containerLookup,
} = useCharacterEngine();
const { ruleData, characterTheme, itemUtils, helperUtils, containerLookup } =
useCharacterEngine();
const actions = useEquipmentMethods();
const image = itemUtils.getAvatarUrl(item);
const altText = itemUtils.getName(item);
@@ -81,7 +75,6 @@ export const GearListItem: FC<GearListProps> = ({
ruleData={ruleData}
showCustomize={false}
showActions={false}
proficiencyBonus={proficiencyBonus}
/>
<EquipmentListItemActions item={item} {...actions} {...props} />
</Accordion>
@@ -29,14 +29,8 @@ export const WeaponListItem: FC<WeaponListItemProps> = ({
...props
}) => {
const actions = useEquipmentMethods();
const {
ruleData,
proficiencyBonus,
characterTheme,
itemUtils,
helperUtils,
containerLookup,
} = useCharacterEngine();
const { ruleData, characterTheme, itemUtils, helperUtils, containerLookup } =
useCharacterEngine();
const image = itemUtils.getAvatarUrl(item);
const altText = itemUtils.getName(item);
const type = itemUtils.getType(item);
@@ -80,7 +74,6 @@ export const WeaponListItem: FC<WeaponListItemProps> = ({
ruleData={ruleData}
showCustomize={false}
showActions={false}
proficiencyBonus={proficiencyBonus}
/>
<EquipmentListItemActions item={item} {...actions} {...props} />
</Accordion>
@@ -1,228 +0,0 @@
import React from "react";
import {
DisabledChevronDownSvg,
DisabledChevronUpSvg,
} from "@dndbeyond/character-components/es";
export class CollapsibleHeading extends React.PureComponent<{
className: string;
}> {
static defaultProps = {
className: "",
};
render() {
const { className, children } = this.props;
return (
<div className={["collapsible-heading", className].join(" ")}>
{children}
</div>
);
}
}
export class CollapsibleHeaderCallout extends React.PureComponent<{
extra: any;
value: any;
}> {
render() {
let { extra, value } = this.props;
return (
<span className="collapsible-header-callout">
<span className="collapsible-header-callout-extra">{extra}</span>
<span className="collapsible-header-callout-value">{value}</span>
</span>
);
}
}
export class CollapsibleHeader extends React.PureComponent<{
clsIdent?: string;
imgSrc?: string;
imgKey?: string;
iconClsNames: Array<string>;
heading: any;
metaItems: Array<string>;
callout?: any;
}> {
static defaultProps = {
iconClsNames: [],
metaItems: [],
};
constructMetaItems() {
let { metaItems } = this.props;
if (!metaItems) {
return null;
}
return (
<div className="collapsible-header-meta">
{metaItems.map((item, idx) => {
return (
<span className="collapsible-header-meta-item" key={item + idx}>
{item}
</span>
);
})}
</div>
);
}
render() {
const { clsIdent, heading, callout, imgSrc, imgKey, iconClsNames } =
this.props;
let calloutNode, headingNode;
if (typeof heading === "string") {
headingNode = (
<CollapsibleHeading className="collapsible-heading">
{heading}
</CollapsibleHeading>
);
} else {
headingNode = heading;
}
if (typeof callout === "string") {
calloutNode = (
<div className="collapsible-heading-callout">{callout}</div>
);
} else {
calloutNode = (
<div className="collapsible-heading-callout">{callout}</div>
);
}
//TODO clean this up and use all of the same thing
let iconStyles = {};
let allIconClsNames = ["collapsible-header-icon"];
let hasIcon = false;
if (imgSrc) {
iconStyles["backgroundImage"] = `url(${imgSrc})`;
hasIcon = true;
} else if (imgKey) {
allIconClsNames.push(`collapsible-header-icon-${imgKey}`);
hasIcon = true;
} else if (iconClsNames.length) {
allIconClsNames = [...allIconClsNames, ...iconClsNames];
hasIcon = true;
}
//TODO fix name to conClsNames for consistency
let conClassNames = [`collapsible-header-el`];
if (clsIdent) {
conClassNames.push(`collapsible-header-el-${clsIdent}`);
}
return (
<div className={conClassNames.join(" ")}>
{hasIcon && (
<div className={allIconClsNames.join(" ")} style={iconStyles} />
)}
<div className="collapsible-header-info">
{headingNode}
{this.constructMetaItems()}
</div>
{calloutNode}
</div>
);
}
}
export class Collapsible extends React.PureComponent<
{
trigger: any;
clsNames: Array<string>;
initiallyCollapsed: boolean;
onChange?: (isCollapsed: boolean) => void;
onOpened?: () => void;
onClosed?: () => void;
},
{
collapsed: boolean;
mouseOverHeading: boolean;
}
> {
static defaultProps = {
clsNames: [],
initiallyCollapsed: true,
};
constructor(props) {
super(props);
this.state = {
collapsed: props.initiallyCollapsed,
mouseOverHeading: false,
};
}
handleUpdate = () => {
const { onChange, onOpened, onClosed } = this.props;
const { collapsed } = this.state;
if (collapsed) {
if (onClosed) {
onClosed();
}
} else {
if (onOpened) {
onOpened();
}
}
if (onChange) {
onChange(collapsed);
}
};
handleClick = (evt) => {
this.setState(
(prevState, preProps) => ({
collapsed: !prevState.collapsed,
}),
this.handleUpdate
);
};
render() {
const { collapsed } = this.state;
const { trigger, clsNames, children } = this.props;
let headerContent = trigger;
if (typeof headerContent == "string") {
headerContent = (
<div className="collapsible-heading">{headerContent}</div>
);
}
let conClassNames = ["collapsible"];
if (clsNames) {
conClassNames = [...clsNames, ...conClassNames];
}
conClassNames.push(
this.state.collapsed ? "collapsible-collapsed" : "collapsible-opened"
);
let headerClassNames = ["collapsible-header"];
if (this.state.mouseOverHeading) {
headerClassNames.push("collapsible-header-over");
}
return (
<div className={conClassNames.join(" ")}>
<div className={headerClassNames.join(" ")} onClick={this.handleClick}>
<div className="collapsible-header-content">{headerContent}</div>
{collapsed ? <DisabledChevronDownSvg /> : <DisabledChevronUpSvg />}
</div>
{collapsed ? null : <div className="collapsible-body">{children}</div>}
</div>
);
}
}
export default Collapsible;
@@ -1,13 +0,0 @@
import Collapsible, {
CollapsibleHeader,
CollapsibleHeaderCallout,
CollapsibleHeading,
} from "./Collapsible";
export default Collapsible;
export {
Collapsible,
CollapsibleHeader,
CollapsibleHeading,
CollapsibleHeaderCallout,
};