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,415 @@
import React, { useContext } from "react";
import { connect, DispatchProp } from "react-redux";
import { FeatureFlagContext } from "@dndbeyond/character-components/es";
import {
characterActions,
CharacterCurrencyContract,
CharacterLifestyleContract,
CoinManager,
Container,
ContainerUtils,
FormatUtils,
RuleData,
rulesEngineSelectors,
serviceDataSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { PaneIdentifiersCurrencyContext } from "~/subApps/sheet/components/Sidebar/types";
import CurrencyCollapsible from "../../../../CharacterSheet/components/CurrencyCollapsible";
import Lifestyle from "../../../../CharacterSheet/components/Lifestyle";
import SettingsButton from "../../../../CharacterSheet/components/SettingsButton";
import * as toastActions from "../../../actions/toastMessage/actions";
import { CURRENCY_VALUE } from "../../../constants/App";
import { CoinManagerContext } from "../../../managers/CoinManagerContext";
import * as appEnvSelectors from "../../../selectors/appEnv";
import { SharedAppState } from "../../../stores/typings";
import { SettingsContextsEnum } from "../SettingsPane/typings";
import { CurrencyErrorTypeEnum } from "./CurrencyPaneConstants";
interface Props extends DispatchProp {
lifestyle: CharacterLifestyleContract | null;
coin: CharacterCurrencyContract;
ruleData: RuleData;
isReadonly: boolean;
identifiers: PaneIdentifiersCurrencyContext | null;
coinManager: CoinManager;
characterContainers: Array<Container>;
partyContainers: Array<Container>;
}
class CurrencyPane extends React.PureComponent<Props> {
hasCurrencyValueChanged = (
value: number,
currencyKey: keyof CharacterCurrencyContract,
coin: CharacterCurrencyContract
): boolean => {
return value !== coin[currencyKey];
};
handleCurrencyChangeError = (
currencyName: string,
errorType: CurrencyErrorTypeEnum
): void => {
const { dispatch } = this.props;
let message: string = "";
if (errorType === CurrencyErrorTypeEnum.MIN) {
message =
"Cannot set currency to a negative value, the previous amount has been set instead.";
}
if (errorType === CurrencyErrorTypeEnum.MAX) {
message = `The max amount allowed for each currency type is ${FormatUtils.renderLocaleNumber(
CURRENCY_VALUE.MAX
)}, the previous value has been set instead.`;
}
if (errorType !== null) {
dispatch(
toastActions.toastError(
`Unable to Set Currency: ${currencyName}`,
message
)
);
}
};
handleCurrencyAdjust = (
coin: Partial<CharacterCurrencyContract>,
multiplier: 1 | -1,
containerDefinitionKey: string
): void => {
const { coinManager, dispatch } = this.props;
let actionLabel: string = "";
if (multiplier === 1) {
actionLabel = "Add";
} else if (multiplier === -1) {
actionLabel = "Delete";
}
coinManager.handleTransaction(
{ coin, containerDefinitionKey, multiplier },
() => {
// dispatch(toastActions.toastSuccess(
// `A ${containerDefinitionKey === coinManager.getPartyEquipmentContainerDefinitionKey() ? 'Party' : ''}Coin transaction was completed!`,
// 'could list the updates?'
// ));
},
() => {
dispatch(
toastActions.toastError(
`Unable to make transaction: ${actionLabel} Coin`,
"Cannot set currency to a negative value, the previous amount has been set instead."
)
);
}
);
};
handleAmountSet = (
containerDefinitionKey: string,
key: keyof CharacterCurrencyContract,
amount: number
): void => {
const { coinManager } = this.props;
const coin = coinManager.getContainerCoin(containerDefinitionKey);
if (coin && this.hasCurrencyValueChanged(amount, key, coin)) {
coinManager.handleAmountSet({
key,
amount,
containerDefinitionKey,
});
}
};
handleLifestyleUpdate = (propertyKey: string, value: number | null): void => {
const { dispatch } = this.props;
dispatch(characterActions.lifestyleSet(value));
};
renderCharacterCoin = (characterContainer: Container) => {
const { isReadonly, ruleData, lifestyle } = this.props;
return (
<React.Fragment>
<div
role="heading"
aria-level={2}
className="ct-currency-pane__subheader"
>
My Coin
</div>
<CurrencyCollapsible
heading="Total (in gp)"
initiallyCollapsed={false}
isReadonly={isReadonly}
container={characterContainer}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(this)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
<Lifestyle
isReadonly={isReadonly}
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
lifestyle={lifestyle}
ruleData={ruleData}
/>
</React.Fragment>
);
};
renderWithoutCointainers() {
const {
identifiers,
coinManager,
characterContainers,
partyContainers,
isReadonly,
lifestyle,
ruleData,
} = this.props;
const containerDefinitionKeyContext =
identifiers?.containerDefinitionKeyContext;
const partyDefinitionKey =
coinManager.getPartyEquipmentContainerDefinitionKey();
const characterContainer = characterContainers.find(
(container) =>
ContainerUtils.getDefinitionKey(container) ===
coinManager.getCharacterContainerDefinitionKey()
);
const partyContainer = partyContainers.find(
(container) =>
ContainerUtils.getDefinitionKey(container) === partyDefinitionKey
);
return (
<FeatureFlagContext.Consumer>
{({ imsFlag }) => (
<div className="ct-currency-pane">
<Header
callout={
<SettingsButton
context={SettingsContextsEnum.COIN}
isReadonly={isReadonly}
/>
}
>
Manage Coin
</Header>
{imsFlag &&
coinManager.isSharingTurnedOnOrDeleteOnly() &&
partyDefinitionKey ? (
<React.Fragment>
<div
role="heading"
aria-level={2}
className="ct-currency-pane__subheader"
>
My Coin
</div>
{characterContainer && (
<CurrencyCollapsible
heading={this.deriveCoinLabel(
ContainerUtils.getName(characterContainer)
)}
initiallyCollapsed={
containerDefinitionKeyContext !==
ContainerUtils.getDefinitionKey(characterContainer)
}
isReadonly={isReadonly}
container={characterContainer}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
this
)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
)}
<Lifestyle
isReadonly={isReadonly}
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
lifestyle={lifestyle}
ruleData={ruleData}
/>
<div
role="heading"
aria-level={2}
className="ct-currency-pane__subheader"
>
Party Coin
</div>
{partyContainer && (
<CurrencyCollapsible
heading={this.deriveCoinLabel(
ContainerUtils.getName(partyContainer)
)}
initiallyCollapsed={
containerDefinitionKeyContext !==
ContainerUtils.getDefinitionKey(partyContainer)
}
isReadonly={isReadonly}
container={partyContainer}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
this
)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
)}
</React.Fragment>
) : (
characterContainer && this.renderCharacterCoin(characterContainer)
)}
</div>
)}
</FeatureFlagContext.Consumer>
);
}
deriveCoinLabel = (containerName): string => {
switch (containerName) {
case "Equipment":
return "My Equipment";
default:
return containerName;
}
};
renderWithCointainers() {
const {
coinManager,
partyContainers,
identifiers,
characterContainers,
isReadonly,
lifestyle,
ruleData,
} = this.props;
const partyDefinitionKey =
coinManager.getPartyEquipmentContainerDefinitionKey();
const containerDefinitionKeyContext =
identifiers?.containerDefinitionKeyContext;
return (
<FeatureFlagContext.Consumer>
{({ imsFlag }) => (
<div className="ct-currency-pane">
<Header
callout={
<SettingsButton
context={SettingsContextsEnum.COIN}
isReadonly={isReadonly}
/>
}
>
Manage Coin
</Header>
<div
role="heading"
aria-level={2}
className="ct-currency-pane__subheader"
>
My Coin
</div>
{characterContainers.map((characterContainer) =>
ContainerUtils.getCoin(characterContainer) ? (
<CurrencyCollapsible
heading={this.deriveCoinLabel(
ContainerUtils.getName(characterContainer)
)}
key={ContainerUtils.getDefinitionKey(characterContainer)}
initiallyCollapsed={
containerDefinitionKeyContext !==
ContainerUtils.getDefinitionKey(characterContainer)
}
isReadonly={isReadonly}
container={characterContainer}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
this
)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
) : null
)}
<Lifestyle
isReadonly={isReadonly}
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
lifestyle={lifestyle}
ruleData={ruleData}
/>
{imsFlag &&
coinManager.isSharingTurnedOnOrDeleteOnly() &&
partyDefinitionKey && (
<>
<div
role="heading"
aria-level={2}
className="ct-currency-pane__subheader"
>
Party Coin
</div>
{partyContainers.map((partyContainer) =>
ContainerUtils.getCoin(partyContainer) ? (
<CurrencyCollapsible
heading={this.deriveCoinLabel(
ContainerUtils.getName(partyContainer)
)}
key={ContainerUtils.getDefinitionKey(partyContainer)}
initiallyCollapsed={
containerDefinitionKeyContext !==
ContainerUtils.getDefinitionKey(partyContainer)
}
isReadonly={isReadonly}
container={partyContainer}
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
this
)}
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(
this
)}
handleAmountSet={this.handleAmountSet.bind(this)}
/>
) : null
)}
</>
)}
</div>
)}
</FeatureFlagContext.Consumer>
);
}
render() {
const { coinManager } = this.props;
return coinManager.canUseCointainers()
? this.renderWithCointainers()
: this.renderWithoutCointainers();
}
}
function mapStateToProps(state: SharedAppState) {
return {
coin: rulesEngineSelectors.getCurrencies(state),
partyInfo: serviceDataSelectors.getPartyInfo(state),
ruleData: rulesEngineSelectors.getRuleData(state),
lifestyle: rulesEngineSelectors.getLifestyle(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
characterContainers:
rulesEngineSelectors.getCharacterInventoryContainers(state),
partyContainers: rulesEngineSelectors.getPartyInventoryContainers(state),
};
}
const CurrencyPaneContainer = (props) => {
const { coinManager } = useContext(CoinManagerContext);
return <CurrencyPane coinManager={coinManager} {...props} />;
};
export default connect(mapStateToProps)(CurrencyPaneContainer);
@@ -0,0 +1,223 @@
import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
import RemoveIcon from "@mui/icons-material/Remove";
import React, { useContext } from "react";
import { Button } from "@dndbeyond/character-components/es";
import {
CharacterCurrencyContract,
CoinManager,
Constants,
} from "@dndbeyond/character-rules-engine/es";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
import { ThemeButton } from "../../../../components/common/Button";
import { CURRENCY_VALUE } from "../../../../constants/App";
import { CoinManagerContext } from "../../../../managers/CoinManagerContext";
import CurrencyPaneAdjusterType from "../CurrencyPaneAdjusterType";
interface Props {
onAdjust?: (
currencyTransaction: Partial<CharacterCurrencyContract>,
multiplier: number,
containerDefinitionKey: string
) => void;
containerDefinitionKey: string;
isReadonly: boolean;
coinManager: CoinManager;
}
interface CurrencyPaneAdjusterState {
pp: number | null;
gp: number | null;
sp: number | null;
ep: number | null;
cp: number | null;
}
class CurrencyPaneAdjuster extends React.PureComponent<
Props,
CurrencyPaneAdjusterState
> {
constructor(props) {
super(props);
this.state = {
pp: null,
gp: null,
sp: null,
ep: null,
cp: null,
};
}
convertStateToCoin = (): Partial<CharacterCurrencyContract> => {
const currencyAdjustments = {
...this.state,
};
return Object.keys(currencyAdjustments).reduce((acc, key) => {
if (
currencyAdjustments.hasOwnProperty(key) &&
currencyAdjustments[key] !== null
) {
acc[key] = currencyAdjustments[key];
}
return acc;
}, {});
};
handleAdjust = (multiplier: number): void => {
const { onAdjust, containerDefinitionKey } = this.props;
this.handleReset();
if (onAdjust) {
onAdjust(this.convertStateToCoin(), multiplier, containerDefinitionKey);
}
};
handleAdd = (): void => {
this.handleAdjust(1);
};
handleRemove = (): void => {
this.handleAdjust(-1);
};
handleChange = (currencyKey: string, value: number | null): void => {
this.setState(
(prevState: CurrencyPaneAdjusterState): CurrencyPaneAdjusterState => ({
...prevState,
[currencyKey]:
value === null ? null : Math.min(value, CURRENCY_VALUE.MAX),
})
);
};
handleBlur = (currencyKey: string, value: number | null): void => {
this.setState(
(prevState: CurrencyPaneAdjusterState): CurrencyPaneAdjusterState => ({
...prevState,
[currencyKey]:
value === null ? null : Math.min(value, CURRENCY_VALUE.MAX),
})
);
};
handleReset = (): void => {
this.setState({
pp: null,
gp: null,
sp: null,
ep: null,
cp: null,
});
};
render() {
const { pp, gp, sp, ep, cp } = this.state;
const { isReadonly, containerDefinitionKey, coinManager } = this.props;
return (
<div className="ct-currency-pane__adjuster">
<div className="ct-currency-pane__adjuster-heading">
<Heading>{`Adjust${
containerDefinitionKey ===
coinManager.getPartyEquipmentContainerDefinitionKey()
? " Party"
: ""
} Coin`}</Heading>
</div>
<div className="ct-currency-pane__adjuster-types">
<CurrencyPaneAdjusterType
containerDefinitionKey={containerDefinitionKey}
name="PP"
currencyKey="pp"
onBlur={this.handleBlur}
onChange={this.handleChange}
coinType={Constants.CoinTypeEnum.pp}
value={pp}
isReadonly={isReadonly}
/>
<CurrencyPaneAdjusterType
containerDefinitionKey={containerDefinitionKey}
name="GP"
currencyKey="gp"
onBlur={this.handleBlur}
onChange={this.handleChange}
coinType={Constants.CoinTypeEnum.gp}
value={gp}
isReadonly={isReadonly}
/>
<CurrencyPaneAdjusterType
containerDefinitionKey={containerDefinitionKey}
name="EP"
currencyKey="ep"
onBlur={this.handleBlur}
onChange={this.handleChange}
coinType={Constants.CoinTypeEnum.ep}
value={ep}
isReadonly={isReadonly}
/>
<CurrencyPaneAdjusterType
containerDefinitionKey={containerDefinitionKey}
name="SP"
currencyKey="sp"
onBlur={this.handleBlur}
onChange={this.handleChange}
coinType={Constants.CoinTypeEnum.sp}
value={sp}
isReadonly={isReadonly}
/>
<CurrencyPaneAdjusterType
containerDefinitionKey={containerDefinitionKey}
name="CP"
currencyKey="cp"
onBlur={this.handleBlur}
onChange={this.handleChange}
coinType={Constants.CoinTypeEnum.cp}
value={cp}
isReadonly={isReadonly}
/>
</div>
<div className="ct-currency-pane__adjuster-actions">
<div className="ct-currency-pane__adjuster-action ct-currency-pane__adjuster-action--positive">
<Button size="medium" onClick={this.handleAdd}>
<span className="ct-currency-pane__adjuster-actions-button-content">
<AddIcon sx={{ width: "13px", height: "13px" }} />
Add
</span>
</Button>
</div>
<div className="ct-currency-pane__adjuster-action ct-currency-pane__adjuster-action--negative">
<Button size="medium" onClick={this.handleRemove}>
<span className="ct-currency-pane__adjuster-actions-button-content">
<RemoveIcon sx={{ width: "13px", height: "13px" }} />
Remove
</span>
</Button>
</div>
<div className="ct-currency-pane__adjuster-action">
<ThemeButton
size="medium"
style="outline"
onClick={this.handleReset}
>
<span className="ct-currency-pane__adjuster-actions-button-content">
<CloseIcon sx={{ width: "13px", height: "13px" }} />
Clear
</span>
</ThemeButton>
</div>
</div>
</div>
);
}
}
const CurrencyPaneAdjusterContainer = (props) => {
const { coinManager } = useContext(CoinManagerContext);
return <CurrencyPaneAdjuster coinManager={coinManager} {...props} />;
};
export default CurrencyPaneAdjusterContainer;
@@ -0,0 +1,4 @@
import CurrencyPaneAdjuster from "./CurrencyPaneAdjuster";
export default CurrencyPaneAdjuster;
export { CurrencyPaneAdjuster };
@@ -0,0 +1,107 @@
import React from "react";
import { CoinIcon } from "@dndbeyond/character-components/es";
import {
Constants,
FormatUtils,
HelperUtils,
} from "@dndbeyond/character-rules-engine/es";
import { CURRENCY_VALUE } from "../../../../constants/App";
interface Props {
currencyKey: string;
name: string;
value: number | null;
onBlur?: (currencyKey: string, value: number | null) => void;
onChange?: (currencyKey: string, value: number | null) => void;
minValue: number;
maxValue: number;
isReadonly: boolean;
coinType: Constants.CoinTypeEnum;
containerDefinitionKey: string;
}
interface State {
value: number | null;
}
export default class CurrencyPaneAdjusterType extends React.PureComponent<
Props,
State
> {
static defaultProps = {
minValue: CURRENCY_VALUE.MIN,
maxValue: CURRENCY_VALUE.MAX,
};
constructor(props: Props) {
super(props);
this.state = {
value: props.value,
};
}
handleChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
const { onChange, currencyKey } = this.props;
if (onChange) {
onChange(currencyKey, HelperUtils.parseInputInt(evt.target.value));
}
};
handleBlur = (evt: React.FocusEvent<HTMLInputElement>): void => {
const { onBlur, currencyKey } = this.props;
if (onBlur) {
onBlur(currencyKey, HelperUtils.parseInputInt(evt.target.value));
}
};
render() {
const {
value,
name,
isReadonly,
minValue,
maxValue,
coinType,
containerDefinitionKey,
} = this.props;
let classNames: Array<string> = ["ct-currency-pane__adjuster-type"];
if (name) {
classNames.push(
`ct-currency-pane__adjuster-type--${FormatUtils.slugify(name)}`
);
}
return (
<div className={classNames.join(" ")}>
<div className="ct-currency-pane__adjuster-type-labels">
<div className="ct-currency-pane__icon">
<CoinIcon coinType={coinType} />
</div>
<div
id={`${containerDefinitionKey}-${coinType}-label`}
className="ct-currency-pane__adjuster-type-name"
>
{name}
</div>
</div>
<div className="ct-currency-pane__adjuster-type-value">
<input
aria-labelledby={`${containerDefinitionKey}-${coinType}-label`}
type="number"
className="ct-currency-pane__adjuster-type-value-input"
value={value === null ? "" : value}
onChange={this.handleChange}
onBlur={this.handleBlur}
readOnly={isReadonly}
min={minValue}
max={maxValue}
/>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import CurrencyPaneAdjusterType from "./CurrencyPaneAdjusterType";
export default CurrencyPaneAdjusterType;
export { CurrencyPaneAdjusterType };
@@ -0,0 +1,4 @@
export enum CurrencyErrorTypeEnum {
MIN = "min",
MAX = "max",
}
@@ -0,0 +1,212 @@
import React from "react";
import { CoinIcon } from "@dndbeyond/character-components/es";
import {
HelperUtils,
FormatUtils,
CharacterCurrencyContract,
Constants,
} from "@dndbeyond/character-rules-engine/es";
import { CURRENCY_VALUE } from "../../../../constants/App";
import { CurrencyErrorTypeEnum } from "../CurrencyPaneConstants";
interface Props {
name: string;
value: number | null;
conversion?: string;
onChange: (value: number) => void;
onError?: (errorType: CurrencyErrorTypeEnum) => void;
isReadonly: boolean;
minValue: number;
maxValue: number;
coinType: Constants.CoinTypeEnum;
}
interface State {
value: number | null;
preValue: number | null;
errorType: CurrencyErrorTypeEnum | null;
isEditorVisible: boolean;
}
export default class Currency extends React.PureComponent<Props, State> {
static defaultProps = {
minValue: CURRENCY_VALUE.MIN,
maxValue: CURRENCY_VALUE.MAX,
};
editorRef = React.createRef<HTMLInputElement>();
constructor(props: Props) {
super(props);
this.state = {
value: props.value,
preValue: props.value,
errorType: null,
isEditorVisible: false,
};
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>
): void {
const { value } = this.props;
if (value !== prevProps.value) {
this.setState({
value,
preValue: value,
});
}
}
handleBlur = (evt: React.FocusEvent<HTMLInputElement>): void => {
const { value, preValue, errorType } = this.state;
const { onChange, onError } = this.props;
let parsedValue = HelperUtils.parseInputInt(evt.target.value);
let targetValue: number | null =
errorType === null && parsedValue !== null ? parsedValue : preValue;
if (targetValue !== null && onChange) {
onChange(targetValue);
}
if (errorType !== null && onError) {
onError(errorType);
}
this.setState({
value: targetValue,
preValue: targetValue,
errorType: null,
isEditorVisible: false,
});
};
handleChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
const { minValue, maxValue } = this.props;
let newValue = HelperUtils.parseInputInt(evt.target.value);
let errorType: CurrencyErrorTypeEnum | null = null;
if (newValue !== null) {
if (newValue < minValue) {
errorType = CurrencyErrorTypeEnum.MIN;
} else if (newValue > maxValue) {
errorType = CurrencyErrorTypeEnum.MAX;
}
}
this.setState({
value: newValue,
errorType,
});
};
handleCurrencyClick = (): void => {
this.setState(
{
isEditorVisible: true,
},
() => {
this.editorRef.current && this.editorRef.current.focus();
}
);
};
renderError = (): React.ReactNode => {
const { errorType } = this.state;
if (errorType === null) {
return null;
}
let errorMessage: React.ReactNode;
if (errorType === CurrencyErrorTypeEnum.MIN) {
errorMessage =
"Cannot accept a negative value. To remove currency, please use the Currency Adjuster options below.";
}
if (errorType === CurrencyErrorTypeEnum.MAX) {
errorMessage = `The max amount allowed is ${FormatUtils.renderLocaleNumber(
CURRENCY_VALUE.MAX
)}.`;
}
return (
<div className="ct-currency-pane__currency-error">
<div className="ct-currency-pane__currency-error-text">
{errorMessage}
</div>
</div>
);
};
renderCurrencyValue = (): React.ReactNode => {
const { value, isEditorVisible } = this.state;
const { isReadonly, minValue, maxValue } = this.props;
if (isEditorVisible) {
return (
<input
ref={this.editorRef}
type="number"
className="ct-currency-pane__currency-value-input"
value={value === null ? "" : value}
onBlur={this.handleBlur}
onChange={this.handleChange}
readOnly={isReadonly}
min={minValue}
max={maxValue}
/>
);
}
return (
<div
className="ct-currency-pane__currency-value-text"
onClick={this.handleCurrencyClick}
>
{value === null ? "" : FormatUtils.renderLocaleNumber(value)}
</div>
);
};
render() {
const { name, conversion, coinType } = this.props;
let classNames: Array<string> = ["ct-currency-pane__currency"];
if (name) {
classNames.push(
`ct-currency-pane__currency--${FormatUtils.slugify(name)}`
);
}
let conversionNode: React.ReactNode;
if (conversion) {
conversionNode = (
<div className="ct-currency-pane__currency-conversion">
{conversion}
</div>
);
}
return (
<div className={classNames.join(" ")}>
<div className="ct-currency-pane__currency-row">
<div className="ct-currency-pane__currency-icon">
<CoinIcon coinType={coinType} />
</div>
<div className="ct-currency-pane__currency-info">
<div className="ct-currency-pane__currency-name">{name}</div>
{conversionNode}
</div>
<div className="ct-currency-pane__currency-value">
{this.renderCurrencyValue()}
</div>
</div>
{this.renderError()}
</div>
);
}
}
@@ -0,0 +1,4 @@
import CurrencyPaneCurrencyRow from "./CurrencyPaneCurrencyRow";
export default CurrencyPaneCurrencyRow;
export { CurrencyPaneCurrencyRow };
@@ -0,0 +1,25 @@
import React from "react";
import { FormatUtils } from "@dndbeyond/character-rules-engine/es";
interface Props {
propertyKey: string;
className: string;
}
export default class CurrencyPaneEditor extends React.PureComponent<Props> {
static defaultProps = {
className: "",
};
render() {
const { propertyKey, className } = this.props;
let classNames: Array<string> = [
"ct-currency-pane__editor",
`ct-currency-pane__editor--${FormatUtils.slugify(propertyKey)}`,
className,
];
return <div className={classNames.join(" ")}>{this.props.children}</div>;
}
}
@@ -0,0 +1,4 @@
import CurrencyPaneEditor from "./CurrencyPaneEditor";
export default CurrencyPaneEditor;
export { CurrencyPaneEditor };
@@ -0,0 +1,11 @@
import React from "react";
export default class CurrencyPaneEditorValue extends React.PureComponent {
render() {
return (
<div className="ct-currency-pane__editor-value">
{this.props.children}
</div>
);
}
}
@@ -0,0 +1,4 @@
import CurrencyPaneEditorValue from "./CurrencyPaneEditorValue";
export default CurrencyPaneEditorValue;
export { CurrencyPaneEditorValue };
@@ -0,0 +1,71 @@
import React from "react";
import { Select } from "@dndbeyond/character-components/es";
import {
HelperUtils,
HtmlSelectOption,
} from "@dndbeyond/character-rules-engine/es";
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
import CurrencyPaneEditor from "../CurrencyPaneEditor";
import CurrencyPaneEditorValue from "../CurrencyPaneEditorValue";
interface Props {
label: string;
propertyKey: string;
defaultValue: number | null;
options: Array<HtmlSelectOption>;
onUpdate?: (propertyKey: string, value: number | null) => void;
isReadonly: boolean;
}
interface State {
value: number | null;
}
export default class CurrencyPaneSelectEditor extends React.PureComponent<
Props,
State
> {
constructor(props: Props) {
super(props);
this.state = {
value: props.defaultValue,
};
}
handleChange = (value) => {
const { propertyKey, onUpdate } = this.props;
if (onUpdate) {
onUpdate(propertyKey, HelperUtils.parseInputInt(value));
this.setState({
value,
});
}
};
render() {
const { value } = this.state;
const { propertyKey, label, options, isReadonly } = this.props;
return (
<CurrencyPaneEditor
propertyKey={propertyKey}
className="ct-currency-pane__editor--select"
>
<Heading>{label}</Heading>
<CurrencyPaneEditorValue>
<Select
className="ct-currency-pane__editor-input"
placeholder="--"
options={options}
value={value}
onChange={this.handleChange}
isReadonly={isReadonly}
/>
</CurrencyPaneEditorValue>
</CurrencyPaneEditor>
);
}
}
@@ -0,0 +1,4 @@
import CurrencyPaneSelectEditor from "./CurrencyPaneSelectEditor";
export default CurrencyPaneSelectEditor;
export { CurrencyPaneSelectEditor };
@@ -0,0 +1,18 @@
import CurrencyPane from "./CurrencyPane";
import CurrencyPaneAdjuster from "./CurrencyPaneAdjuster";
import CurrencyPaneAdjusterType from "./CurrencyPaneAdjusterType";
import CurrencyPaneCurrencyRow from "./CurrencyPaneCurrencyRow";
import CurrencyPaneEditor from "./CurrencyPaneEditor";
import CurrencyPaneEditorValue from "./CurrencyPaneEditorValue";
import CurrencyPaneSelectEditor from "./CurrencyPaneSelectEditor";
export default CurrencyPane;
export {
CurrencyPane,
CurrencyPaneAdjuster,
CurrencyPaneAdjusterType,
CurrencyPaneCurrencyRow,
CurrencyPaneEditor,
CurrencyPaneEditorValue,
CurrencyPaneSelectEditor,
};