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,208 @@
|
||||
import clsx from "clsx";
|
||||
import { FC, HTMLAttributes, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
CharacterUtils,
|
||||
FormatUtils,
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
RuleDataUtils,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import XpBar from "~/tools/js/smartComponents/XpBar";
|
||||
import { Select } from "~/tools/js/smartComponents/legacy";
|
||||
|
||||
import { Button } from "../Button";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
const XP_CHANGE_TYPE = {
|
||||
ADD: "ADD",
|
||||
REMOVE: "REMOVE",
|
||||
};
|
||||
|
||||
interface XpManagerProps extends HTMLAttributes<HTMLDivElement> {
|
||||
handleXpUpdates: (newXpTotal: number) => void;
|
||||
shouldReset: boolean;
|
||||
}
|
||||
|
||||
export const XpManager: FC<XpManagerProps> = ({
|
||||
handleXpUpdates,
|
||||
shouldReset,
|
||||
...props
|
||||
}) => {
|
||||
const { experienceInfo: xpInfo, ruleData } = useCharacterEngine();
|
||||
|
||||
const [xpNew, setXpNew] = useState<number | null>(null);
|
||||
const [xpChange, setXpChange] = useState<number | null>(null);
|
||||
const [levelChosen, setLevelChosen] = useState<number | null>(null);
|
||||
const [changeType, setChangeType] = useState<string>(XP_CHANGE_TYPE.ADD);
|
||||
const [newXpTotal, setNewXpTotal] = useState<number>(xpInfo.currentLevelXp);
|
||||
|
||||
useEffect(() => {
|
||||
let xpDiff: number = xpChange ?? 0;
|
||||
if (changeType === XP_CHANGE_TYPE.REMOVE) {
|
||||
xpDiff *= -1;
|
||||
}
|
||||
|
||||
let newXpTotal: number = xpInfo.currentLevelXp;
|
||||
if (xpDiff) {
|
||||
newXpTotal += xpDiff;
|
||||
} else if (levelChosen !== null) {
|
||||
newXpTotal = CharacterUtils.deriveCurrentLevelXp(levelChosen, ruleData);
|
||||
} else if (xpNew !== null) {
|
||||
newXpTotal = xpNew;
|
||||
}
|
||||
|
||||
newXpTotal = Math.min(
|
||||
Math.max(0, newXpTotal),
|
||||
CharacterUtils.deriveMaxXp(ruleData)
|
||||
);
|
||||
|
||||
setNewXpTotal((prev) => {
|
||||
if (newXpTotal !== prev) {
|
||||
handleXpUpdates(newXpTotal);
|
||||
}
|
||||
return newXpTotal;
|
||||
});
|
||||
}, [
|
||||
xpChange,
|
||||
changeType,
|
||||
levelChosen,
|
||||
xpNew,
|
||||
xpInfo,
|
||||
ruleData,
|
||||
handleXpUpdates,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldReset) {
|
||||
setXpNew(null);
|
||||
setXpChange(null);
|
||||
setLevelChosen(null);
|
||||
setChangeType(XP_CHANGE_TYPE.ADD);
|
||||
setNewXpTotal(xpInfo.currentLevelXp);
|
||||
}
|
||||
}, [shouldReset, xpInfo]);
|
||||
|
||||
const levelOptions = useMemo(() => {
|
||||
let levelOptions: Array<HtmlSelectOption> = [];
|
||||
for (let i = 1; i <= RuleDataUtils.getMaxCharacterLevel(ruleData); i++) {
|
||||
levelOptions.push({
|
||||
label: `${i}`,
|
||||
value: i,
|
||||
});
|
||||
}
|
||||
|
||||
return levelOptions;
|
||||
}, [ruleData]);
|
||||
|
||||
const displayCurrentLevelXp = useMemo(() => {
|
||||
return xpInfo.currentLevel === ruleData.maxCharacterLevel
|
||||
? CharacterUtils.deriveCurrentLevelXp(
|
||||
ruleData.maxCharacterLevel - 1,
|
||||
ruleData
|
||||
)
|
||||
: xpInfo.currentLevelXp;
|
||||
}, [xpInfo, ruleData]);
|
||||
|
||||
const handleChooseLevel = (value: string): void => {
|
||||
setXpNew(null);
|
||||
setXpChange(null);
|
||||
setLevelChosen(HelperUtils.parseInputInt(value));
|
||||
};
|
||||
|
||||
const handleXpChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setXpNew(null);
|
||||
setXpChange(HelperUtils.parseInputInt(evt.target.value));
|
||||
setLevelChosen(null);
|
||||
};
|
||||
|
||||
const handleXpSet = (evt: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setXpNew(HelperUtils.parseInputInt(evt.target.value));
|
||||
setXpChange(null);
|
||||
setLevelChosen(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
<h2 className={styles.levelTotal}>
|
||||
Current XP Total:{" "}
|
||||
{FormatUtils.renderLocaleNumber(xpInfo.currentLevelXp)} (Level{" "}
|
||||
{xpInfo.currentLevel})
|
||||
</h2>
|
||||
<div className={styles.xpBar}>
|
||||
<XpBar
|
||||
xp={xpInfo.currentLevelXp}
|
||||
showCurrentMarker={true}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.currentLevelAmounts}>
|
||||
<div>{FormatUtils.renderLocaleNumber(displayCurrentLevelXp)}</div>
|
||||
<div>{FormatUtils.renderLocaleNumber(xpInfo.nextLevelXp)}</div>
|
||||
</div>
|
||||
<div className={clsx([styles.controls, styles.withDivider])}>
|
||||
<div className={styles.control}>
|
||||
<label id="set-level" className={styles.label}>
|
||||
Set Level
|
||||
</label>
|
||||
<Select
|
||||
aria-labelledby="set-level"
|
||||
options={levelOptions}
|
||||
onChange={handleChooseLevel}
|
||||
value={levelChosen}
|
||||
placeholder={"--"}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx([styles.control, styles.setXp])}>
|
||||
<label id="set-xp" className={styles.label}>
|
||||
Set XP
|
||||
</label>
|
||||
<input
|
||||
aria-labelledby="set-xp"
|
||||
type="number"
|
||||
value={xpNew === null ? "" : xpNew}
|
||||
onChange={handleXpSet}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.adjustGroup}>
|
||||
<div className={styles.adjustTabs}>
|
||||
<Button
|
||||
variant="text"
|
||||
size="xx-small"
|
||||
className={clsx([
|
||||
styles.tab,
|
||||
changeType === XP_CHANGE_TYPE.ADD && styles.active,
|
||||
])}
|
||||
onClick={() => setChangeType(XP_CHANGE_TYPE.ADD)}
|
||||
>
|
||||
Add Xp
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
size="xx-small"
|
||||
className={clsx([
|
||||
styles.tab,
|
||||
changeType === XP_CHANGE_TYPE.REMOVE && styles.active,
|
||||
])}
|
||||
onClick={() => setChangeType(XP_CHANGE_TYPE.REMOVE)}
|
||||
>
|
||||
Remove Xp
|
||||
</Button>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
value={xpChange === null ? "" : xpChange}
|
||||
onChange={handleXpChange}
|
||||
placeholder="Type XP Value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className={clsx([styles.levelTotal, styles.withDivider])}>
|
||||
New XP Total: {FormatUtils.renderLocaleNumber(newXpTotal)} (Level{" "}
|
||||
{CharacterUtils.deriveXpLevel(newXpTotal, ruleData)})
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user