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,181 @@
import { groupBy, orderBy } from "lodash";
import React, { useContext } from "react";
import { connect } from "react-redux";
import { AttunementSlot } from "@dndbeyond/character-components/es";
import {
rulesEngineSelectors,
Item,
ItemUtils,
CampaignUtils,
CharacterTheme,
InventoryManager,
serviceDataSelectors,
PartyInfo,
} from "@dndbeyond/character-rules-engine/es";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { InventoryManagerContext } from "../../../Shared/managers/InventoryManagerContext";
import { PaneIdentifierUtils } from "../../../Shared/utils";
import { SheetAppState } from "../../typings";
import AttunementItem from "./AttunementItem";
interface Props {
items: Array<Item>;
attunedSlots: Array<Item | null>;
isFiltered: boolean;
isMobile: boolean;
theme: CharacterTheme;
inventoryManager: InventoryManager;
partyInfo: PartyInfo | null;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class Attunement extends React.PureComponent<Props> {
static defaultProps = {
isFiltered: false,
isMobile: false,
};
handleItemShow = (item: Item): void => {
const { paneHistoryStart } = this.props;
paneHistoryStart(
PaneComponentEnum.ITEM_DETAIL,
PaneIdentifierUtils.generateItem(ItemUtils.getMappingId(item))
);
};
renderItems = (): React.ReactNode => {
const { items, theme, inventoryManager, partyInfo } = this.props;
const attunableItems = items.filter((item) => ItemUtils.canAttune(item));
const sortedItems = groupBy(
orderBy(
attunableItems,
[
(item) => ItemUtils.getRarityLevel(item),
(item) => ItemUtils.getName(item),
(item) => ItemUtils.getMappingId(item),
],
["desc", "asc"]
),
(item) => {
if (inventoryManager.isShared(item)) {
return "party";
} else {
return "character";
}
}
);
return (
<div className="ct-attunement__group ct-attunement__group--items">
<div className="ct-attunement__group-header">
Items Requiring Attunement
</div>
<div className="ct-attunement__group-items">
{sortedItems.character?.length > 0 ? (
sortedItems.character?.map((item) => (
<AttunementItem
key={ItemUtils.getUniqueKey(item)}
item={item}
onItemShow={this.handleItemShow}
theme={theme}
/>
))
) : (
<div className="ct-attunement__group-empty">
Items that you can attune to will display here as you make them
active.
</div>
)}
</div>
{partyInfo &&
CampaignUtils.isSharingStateActive(
CampaignUtils.getSharingState(partyInfo)
) && (
<>
<div className="ct-attunement__group-header">
Party Items Requiring Attunement
</div>
<div className="ct-attunement__group-items">
{sortedItems.party?.length > 0 ? (
sortedItems.party?.map((item) => (
<AttunementItem
key={ItemUtils.getUniqueKey(item)}
item={item}
onItemShow={this.handleItemShow}
theme={theme}
/>
))
) : (
<div className="ct-attunement__group-empty">
Party items that you can attune to will display here as you
make them active.
</div>
)}
</div>
</>
)}
</div>
);
};
renderAttuned = (): React.ReactNode => {
const { attunedSlots, isMobile, theme } = this.props;
return (
<div className="ct-attunement__group ct-attunement__group--attuned">
<div className="ct-attunement__group-header">Attuned Items</div>
<div className="ct-attunement__group-items">
{attunedSlots.map((slot, idx) => (
<AttunementSlot
theme={theme}
key={`slot-${idx}-${slot ? ItemUtils.getUniqueKey(slot) : ""}`}
slot={slot}
onClick={this.handleItemShow}
isMobile={isMobile}
/>
))}
</div>
</div>
);
};
render() {
return (
<div className="ct-attunement">
{this.renderAttuned()}
{this.renderItems()}
</div>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
attunedSlots: rulesEngineSelectors.getAttunedSlots(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
items: rulesEngineSelectors.getEquippedItems(state),
partyInfo: serviceDataSelectors.getPartyInfo(state),
};
}
const AttunementContainer = (props) => {
const { inventoryManager } = useContext(InventoryManagerContext);
const {
pane: { paneHistoryStart },
} = useSidebar();
return (
<Attunement
inventoryManager={inventoryManager}
paneHistoryStart={paneHistoryStart}
{...props}
/>
);
};
export default connect(mapStateToProps)(AttunementContainer);
@@ -0,0 +1,149 @@
import React, { useContext } from "react";
import {
CharacterTheme,
InventoryManager,
Item,
ItemUtils,
} from "@dndbeyond/character-rules-engine/es";
import { ItemName } from "~/components/ItemName";
import { ItemSlotManager } from "../../../../Shared/components/ItemSlotManager";
import { InventoryManagerContext } from "../../../../Shared/managers/InventoryManagerContext";
interface Props {
item: Item;
onItemShow?: (item: Item) => void;
className: string;
theme: CharacterTheme;
inventoryManager: InventoryManager;
}
export class AttunementItem extends React.PureComponent<Props> {
static defaultProps = {
className: "",
};
handleSetAttune = (uses: number): void => {
const { inventoryManager, item } = this.props;
//TODO use different component than SlotManager for attune/unattune
if (uses === 1) {
inventoryManager.handleAttune({ item });
}
if (uses === 0) {
inventoryManager.handleUnattune({ item });
}
};
handleItemClick = (evt: React.MouseEvent): void => {
const { onItemShow, item } = this.props;
evt.nativeEvent.stopImmediatePropagation();
evt.stopPropagation();
if (onItemShow) {
onItemShow(item);
}
};
renderMetaItems = (): React.ReactNode => {
const { item, inventoryManager } = this.props;
let metaItems: Array<string> = [];
if (ItemUtils.isLegacy(item)) {
metaItems.push("Legacy");
}
let type = ItemUtils.getType(item);
if (type !== null) {
metaItems.push(type);
}
if (ItemUtils.isWeaponContract(item)) {
if (ItemUtils.isOffhand(item)) {
metaItems.push("Dual Wield");
}
} else if (ItemUtils.isArmorContract(item)) {
let baseArmorName = ItemUtils.getBaseArmorName(item);
if (baseArmorName) {
metaItems.push(baseArmorName);
}
} else if (ItemUtils.isGearContract(item)) {
let subType = ItemUtils.getSubType(item);
if (subType) {
metaItems.push(subType);
}
if (ItemUtils.isOffhand(item)) {
metaItems.push("Dual Wield");
}
}
if (
ItemUtils.isEquipped(item) &&
!inventoryManager.isEquippedToCurrentCharacter(item)
) {
metaItems.push(
`${
ItemUtils.isAttuned(item) ? "Attuned" : "Equipped"
} by ${inventoryManager.getEquippedCharacterName(item)}`
);
}
return (
<div className="ct-attunement__meta">
{metaItems.map((metaItem, idx) => (
<span className="ct-attunement__meta-item" key={idx}>
{metaItem}
</span>
))}
</div>
);
//`
};
render() {
const { item, className, theme, inventoryManager } = this.props;
const canAttune = inventoryManager.canAttuneUnattuneItem(item);
return (
<div
className={`ct-attunement__item ${className}`}
onClick={this.handleItemClick}
>
<div className="ct-attunement__item-action">
<ItemSlotManager
isUsed={!!ItemUtils.isAttuned(item)}
canUse={canAttune}
theme={theme}
onSet={this.handleSetAttune}
useTooltip={
!!ItemUtils.isEquipped(item) &&
!inventoryManager.isEquippedToCurrentCharacter(item)
}
tooltipTitle={`${
ItemUtils.isAttuned(item) ? "Attuned" : "Equipped"
} by ${inventoryManager.getEquippedCharacterName(item)}`}
/>
</div>
<div className="ct-attunement__item-name">
<div className="ct-attunement__item-heading">
<ItemName item={item} />
</div>
{this.renderMetaItems()}
</div>
</div>
);
//`
}
}
const AttunementItemContainer = (props) => {
const { inventoryManager } = useContext(InventoryManagerContext);
return <AttunementItem inventoryManager={inventoryManager} {...props} />;
};
export default AttunementItemContainer;
@@ -0,0 +1,4 @@
import AttunementItem from "./AttunementItem";
export default AttunementItem;
export { AttunementItem };
@@ -0,0 +1,5 @@
import Attunement from "./Attunement";
import AttunementItem from "./AttunementItem";
export default Attunement;
export { Attunement, AttunementItem };