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,220 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
|
||||
import { Collapsible, Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
characterActions,
|
||||
CharacterPreferences,
|
||||
Constants,
|
||||
CustomSpeedContract,
|
||||
CustomSpeedLookup,
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
SpeedInfo,
|
||||
RuleData,
|
||||
RuleDataUtils,
|
||||
rulesEngineSelectors,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
import { MOVEMENT_TYPE_LIST } from "../../../constants/App";
|
||||
import * as appEnvSelectors from "../../../selectors/appEnv";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import SpeedManagePaneCustomizeItem from "./SpeedManagePaneCustomizeItem";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
movements: SpeedInfo;
|
||||
customSpeedLookup: CustomSpeedLookup;
|
||||
customSpeeds: Array<CustomSpeedContract>;
|
||||
preferences: CharacterPreferences;
|
||||
ruleData: RuleData;
|
||||
isReadonly: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class SpeedManagePane extends React.PureComponent<Props> {
|
||||
handleMovementDisplayChange = (value: string): void => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(
|
||||
characterActions.preferenceChoose(
|
||||
"primaryMovement",
|
||||
HelperUtils.parseInputInt(value)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
handleOverrideUpdate = (
|
||||
id: number,
|
||||
distance: number | null,
|
||||
source: string | null
|
||||
): void => {
|
||||
const { dispatch, customSpeeds } = this.props;
|
||||
|
||||
if (distance !== null || source !== null) {
|
||||
let foundSpeed: CustomSpeedContract | undefined = customSpeeds.find(
|
||||
(speed) => speed.movementId === id
|
||||
);
|
||||
|
||||
if (foundSpeed) {
|
||||
dispatch(
|
||||
characterActions.movementSet({
|
||||
...foundSpeed,
|
||||
distance,
|
||||
source,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
let newSpeed: CustomSpeedContract = {
|
||||
movementId: id,
|
||||
distance,
|
||||
source,
|
||||
};
|
||||
dispatch(characterActions.movementAdd(newSpeed));
|
||||
}
|
||||
} else {
|
||||
dispatch(characterActions.movementRemove(id));
|
||||
}
|
||||
};
|
||||
|
||||
renderCustomize = (): React.ReactNode => {
|
||||
const { customSpeedLookup, preferences, isReadonly, ruleData } = this.props;
|
||||
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
let movementOptions: Array<HtmlSelectOption> = MOVEMENT_TYPE_LIST.map(
|
||||
(key) => ({
|
||||
label: RuleDataUtils.getMovementDescription(key, ruleData),
|
||||
value: key,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
layoutType={"minimal"}
|
||||
header="Customize"
|
||||
className="ct-speed-manage-pane__customize"
|
||||
>
|
||||
<Heading>Override Speeds</Heading>
|
||||
<div className="ct-speed-manage-pane__customize-header">
|
||||
<div className="ct-speed-manage-pane__customize-header-label" />
|
||||
<div className="ct-speed-manage-pane__customize-header-input">
|
||||
Speed (ft.)
|
||||
</div>
|
||||
<div className="ct-speed-manage-pane__customize-header-source">
|
||||
Source/Notes
|
||||
</div>
|
||||
</div>
|
||||
{movementOptions.map((option: HtmlSelectOption) => {
|
||||
const override = customSpeedLookup[option.value];
|
||||
return (
|
||||
<SpeedManagePaneCustomizeItem
|
||||
key={option.value as number}
|
||||
id={option.value as number}
|
||||
label={option.label}
|
||||
initialValue={override ? override.distance : null}
|
||||
initialSource={override ? override.source : null}
|
||||
minimumValue={0}
|
||||
onUpdate={this.handleOverrideUpdate}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Heading>Set Movement Display</Heading>
|
||||
<Select
|
||||
options={movementOptions}
|
||||
onChange={this.handleMovementDisplayChange}
|
||||
initialOptionRemoved={true}
|
||||
value={preferences.primaryMovement}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
renderDescription = (): React.ReactNode => {
|
||||
const { ruleData } = this.props;
|
||||
|
||||
let rule = RuleDataUtils.getRule(Constants.RuleKeyEnum.SPEED, ruleData);
|
||||
if (rule === null || rule.description === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HtmlContent
|
||||
className="ct-speed-manage-pane__description"
|
||||
html={rule.description}
|
||||
withoutTooltips
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { movements, customSpeedLookup, ruleData, theme } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-speed-manage-pane">
|
||||
<Header>Speed</Header>
|
||||
<div className="ct-speed-manage-pane__speeds">
|
||||
{Object.keys(movements).map(
|
||||
(movementKey: Constants.SpeedMovementKeyEnum) => {
|
||||
const movementAmount = movements[movementKey];
|
||||
if (movementAmount === null || movementAmount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let movementType =
|
||||
RuleDataUtils.getMovementTypeBySpeedMovementKey(movementKey);
|
||||
let customSpeed: CustomSpeedContract | null | undefined =
|
||||
movementType === null ? null : customSpeedLookup[movementType];
|
||||
let source: string =
|
||||
customSpeed && customSpeed.source ? customSpeed.source : "";
|
||||
|
||||
return (
|
||||
<div className="ct-speed-manage-pane__speed" key={movementKey}>
|
||||
<span className="ct-speed-manage-pane__speed-label">
|
||||
{RuleDataUtils.getSpeedMovementKeyLabel(
|
||||
movementKey,
|
||||
ruleData
|
||||
)}
|
||||
</span>
|
||||
<span className="ct-speed-manage-pane__speed-amount">
|
||||
<NumberDisplay
|
||||
type="distanceInFt"
|
||||
number={movementAmount}
|
||||
/>
|
||||
</span>
|
||||
{source && (
|
||||
<span className="ct-speed-manage-pane__speed-source">
|
||||
({source})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
{this.renderCustomize()}
|
||||
{this.renderDescription()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: SharedAppState) {
|
||||
return {
|
||||
movements: rulesEngineSelectors.getCurrentCarriedWeightSpeed(state),
|
||||
preferences: rulesEngineSelectors.getCharacterPreferences(state),
|
||||
customSpeedLookup: rulesEngineSelectors.getCustomSpeedLookup(state),
|
||||
customSpeeds: rulesEngineSelectors.getCustomSpeeds(state),
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SpeedManagePane);
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
|
||||
import { HelperUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
label: string | null;
|
||||
initialValue: number | null;
|
||||
initialSource: string | null;
|
||||
onUpdate: (id: number, value: number | null, source: string | null) => void;
|
||||
enableSource: boolean;
|
||||
minimumValue: number | null;
|
||||
className: string;
|
||||
}
|
||||
interface State {
|
||||
value: number | null;
|
||||
source: string | null;
|
||||
isDirty: boolean;
|
||||
}
|
||||
export default class SpeedManagePaneCustomizeItem extends React.PureComponent<
|
||||
Props,
|
||||
State
|
||||
> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
enableSource: true,
|
||||
minimumValue: null,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.initialValue,
|
||||
source: props.initialSource,
|
||||
isDirty: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleUpdate = (): void => {
|
||||
const { onUpdate, id } = this.props;
|
||||
const { value, source, isDirty } = this.state;
|
||||
|
||||
if (isDirty && onUpdate) {
|
||||
onUpdate(id, value, source);
|
||||
this.setState({
|
||||
isDirty: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleValueBlur = (evt: React.FocusEvent<HTMLInputElement>): void => {
|
||||
const { minimumValue } = this.props;
|
||||
|
||||
let value = evt.target.value;
|
||||
this.setState((prevState) => {
|
||||
let parsedValue = HelperUtils.parseInputInt(value);
|
||||
let clampedInt: number | null = parsedValue;
|
||||
if (minimumValue !== null && parsedValue !== null) {
|
||||
clampedInt = HelperUtils.clampInt(parsedValue, minimumValue);
|
||||
}
|
||||
|
||||
return {
|
||||
value: clampedInt,
|
||||
};
|
||||
}, this.handleUpdate);
|
||||
};
|
||||
|
||||
handleValueChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({
|
||||
value: HelperUtils.parseInputInt(evt.target.value),
|
||||
isDirty: true,
|
||||
});
|
||||
};
|
||||
|
||||
handleSourceChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({
|
||||
source: evt.target.value,
|
||||
isDirty: true,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value, source } = this.state;
|
||||
const { label, enableSource, minimumValue, className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={`ct-speed-manage-pane__customize-item ${className}`}>
|
||||
<div className="ct-speed-manage-pane__customize-item-label">
|
||||
{label}
|
||||
</div>
|
||||
<div className="ct-speed-manage-pane__customize-item-input">
|
||||
<input
|
||||
type="number"
|
||||
min={minimumValue === null ? undefined : minimumValue}
|
||||
onChange={this.handleValueChange}
|
||||
onBlur={this.handleValueBlur}
|
||||
value={value === null ? "" : value}
|
||||
/>
|
||||
</div>
|
||||
{enableSource && (
|
||||
<div className="ct-speed-manage-pane__customize-item-source">
|
||||
<input
|
||||
type="text"
|
||||
onChange={this.handleSourceChange}
|
||||
onBlur={this.handleUpdate}
|
||||
value={source === null ? "" : source}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
//`
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import SpeedManagePaneCustomizeItem from "./SpeedManagePaneCustomizeItem";
|
||||
|
||||
export default SpeedManagePaneCustomizeItem;
|
||||
export { SpeedManagePaneCustomizeItem };
|
||||
@@ -0,0 +1,5 @@
|
||||
import SpeedManagePane from "./SpeedManagePane";
|
||||
import SpeedManagePaneCustomizeItem from "./SpeedManagePaneCustomizeItem";
|
||||
|
||||
export default SpeedManagePane;
|
||||
export { SpeedManagePane, SpeedManagePaneCustomizeItem };
|
||||
Reference in New Issue
Block a user