2025-05-28 15:36:51 -07:00

272 lines
7.3 KiB
TypeScript

import * as React from "react";
import {
Collapsible,
CollapsibleHeaderCallout,
CollapsibleHeaderContent,
} from "@dndbeyond/character-components/es";
import {
HelperUtils,
VehicleFuelData,
} from "@dndbeyond/character-rules-engine/es";
import { QuickActions } from "~/subApps/sheet/components/Sidebar/components/QuickActions/QuickActions";
import SimpleQuantity from "../SimpleQuantity";
import { ThemeButton } from "../common/Button";
interface Props {
fuelData: VehicleFuelData;
onChange: (value: number) => void;
initiallyCollapsed: boolean;
isInteractive: boolean;
defaultAdjusterValue: number;
}
interface State {
remainingFuel: number | null;
adjusterValue: number;
}
export default class VehicleFuelTracker extends React.PureComponent<
Props,
State
> {
static defaultProps = {
initiallyCollapsed: true,
isInteractive: false,
defaultAdjusterValue: 1,
};
remainingFuelInput = React.createRef<HTMLInputElement>();
constructor(props: Props) {
super(props);
this.state = {
remainingFuel: props.fuelData.remainingFuel,
adjusterValue: props.defaultAdjusterValue,
};
}
componentDidUpdate(prevProps: Props, prevState: State): void {
const { fuelData } = this.props;
if (fuelData.remainingFuel !== prevProps.fuelData.remainingFuel) {
this.setState({
remainingFuel: fuelData.remainingFuel,
});
}
}
handleRemainingFuelSet = (newValue: number): void => {
const { onChange, fuelData } = this.props;
if (fuelData.remainingFuel !== newValue && onChange) {
onChange(newValue);
}
this.setState({
remainingFuel: newValue,
});
};
handleRemainingFuelInputBlur = (
evt: React.FocusEvent<HTMLInputElement>
): void => {
this.handleRemainingFuelSet(HelperUtils.parseInputInt(evt.target.value, 0));
};
handleRemainingFuelInputChange = (
evt: React.ChangeEvent<HTMLInputElement>
): void => {
this.setState({
remainingFuel: HelperUtils.parseInputInt(evt.target.value),
});
};
updateAdjusterValue = (newValue: number): void => {
const { adjusterValue } = this.state;
if (newValue !== adjusterValue) {
this.setState({
adjusterValue: newValue,
});
}
};
resetAdjusterValue = (): void => {
const { defaultAdjusterValue } = this.props;
const { adjusterValue } = this.state;
if (adjusterValue !== defaultAdjusterValue) {
this.setState({
adjusterValue: defaultAdjusterValue,
});
}
};
handleAdjusterUse = (): void => {
const { adjusterValue, remainingFuel } = this.state;
let remainingFuelValue: number = remainingFuel === null ? 0 : remainingFuel;
this.handleRemainingFuelSet(
Math.max(remainingFuelValue - adjusterValue, 0)
);
this.resetAdjusterValue();
};
handleAdjusterAdd = (): void => {
const { adjusterValue, remainingFuel } = this.state;
const { fuelData } = this.props;
let remainingFuelValue: number = remainingFuel === null ? 0 : remainingFuel;
let value: number = adjusterValue + remainingFuelValue;
if (fuelData.maxAmount !== null) {
value = Math.max(value, fuelData.maxAmount);
}
this.handleRemainingFuelSet(value);
this.resetAdjusterValue();
};
handleQuickAction = (value: number): void => {
const { fuelData } = this.props;
const { remainingFuel } = this.state;
let remainingFuelValue: number = remainingFuel === null ? 0 : remainingFuel;
let newValue: number = remainingFuelValue + value;
if (fuelData.maxAmount !== null) {
newValue = Math.max(newValue, fuelData.maxAmount);
}
this.handleRemainingFuelSet(newValue);
};
renderRemainingFuelInput = (): React.ReactNode => {
const { remainingFuel } = this.state;
const { fuelData, isInteractive } = this.props;
return (
<div className="ct-vehicle-fuel-tracker__primary">
<div className="ct-vehicle-fuel-tracker__primary-label">
Fuel remaining (HRS)
</div>
<div className="ct-vehicle-fuel-tracker__primary-value">
<input
ref={this.remainingFuelInput}
className="ct-vehicle-fuel-tracker__primary-input"
type="number"
value={remainingFuel !== null ? remainingFuel : ""}
onBlur={this.handleRemainingFuelInputBlur}
onChange={this.handleRemainingFuelInputChange}
min={0}
max={fuelData.maxAmount !== null ? fuelData.maxAmount : undefined}
readOnly={!isInteractive}
/>
</div>
</div>
);
};
renderSimpleQuantityAdjuster = (): React.ReactNode => {
const { adjusterValue } = this.state;
const { isInteractive } = this.props;
return (
<div className="ct-vehicle-fuel-tracker__adjuster">
<div className="ct-vehicle-fuel-tracker__adjuster-simple-quantity">
<SimpleQuantity
label={""}
quantity={adjusterValue}
minimum={1}
maximum={null}
onUpdate={this.updateAdjusterValue}
isReadonly={!isInteractive}
/>
</div>
<div className="ct-vehicle-fuel-tracker__adjuster-actions">
<ThemeButton
size="small"
style="outline"
className="ct-vehicle-fuel-tracker__adjuster-button ct-vehicle-fuel-tracker__adjuster-button--use"
onClick={this.handleAdjusterUse}
isInteractive={isInteractive}
>
Use
</ThemeButton>
<ThemeButton
size="small"
className="ct-vehicle-fuel-tracker__adjuster-button ct-vehicle-fuel-tracker__adjuster-button--add"
onClick={this.handleAdjusterAdd}
isInteractive={isInteractive}
>
Add
</ThemeButton>
</div>
</div>
);
};
renderQuickActions = (): React.ReactNode => {
const { fuelData, isInteractive } = this.props;
if (fuelData.fuelItems === null) {
return null;
}
const actions = [] as {
label: string;
onClick: () => void;
disabled: boolean;
}[];
fuelData.fuelItems.forEach((item) => {
actions.push({
label: `Add ${item.amountPerCharge} HRS`,
onClick: this.handleQuickAction.bind(this, item.amountPerCharge),
disabled: !isInteractive,
});
});
return <QuickActions actions={actions}></QuickActions>;
};
render() {
const { fuelData, initiallyCollapsed } = this.props;
let extraNode: React.ReactNode = (
<div className="ct-vehicle-fuel-tracker__summary">
<span className="ct-vehicle-fuel-tracker__summary-value">
{fuelData.remainingFuel} hrs
</span>
</div>
);
let headerCalloutNode: React.ReactNode = (
<CollapsibleHeaderCallout extra={extraNode} value={null} />
);
let headerNode: React.ReactNode = (
<CollapsibleHeaderContent
heading="Fuel Management"
callout={headerCalloutNode}
/>
);
return (
<div className="ct-vehicle-fuel-tracker">
<Collapsible
header={headerNode}
initiallyCollapsed={initiallyCollapsed}
>
<div className="ct-vehicle-fuel-tracker__content">
{this.renderRemainingFuelInput()}
{this.renderSimpleQuantityAdjuster()}
{this.renderQuickActions()}
</div>
</Collapsible>
</div>
);
}
}