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:
@@ -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);
|
||||
+223
@@ -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 };
|
||||
+107
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import CurrencyPaneAdjusterType from "./CurrencyPaneAdjusterType";
|
||||
|
||||
export default CurrencyPaneAdjusterType;
|
||||
export { CurrencyPaneAdjusterType };
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum CurrencyErrorTypeEnum {
|
||||
MIN = "min",
|
||||
MAX = "max",
|
||||
}
|
||||
+212
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import CurrencyPaneCurrencyRow from "./CurrencyPaneCurrencyRow";
|
||||
|
||||
export default CurrencyPaneCurrencyRow;
|
||||
export { CurrencyPaneCurrencyRow };
|
||||
+25
@@ -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 };
|
||||
+11
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import CurrencyPaneEditorValue from "./CurrencyPaneEditorValue";
|
||||
|
||||
export default CurrencyPaneEditorValue;
|
||||
export { CurrencyPaneEditorValue };
|
||||
+71
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user