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,119 @@
import React, { useCallback } from "react";
import { RollRequest } from "@dndbeyond/dice";
import LoadingPlaceholder from "../../LoadingPlaceholder";
import { DataLoadingStatusEnum } from "../../componentConstants";
import {
DiceRollActionNode,
DiceRollActionNodeProps,
} from "./DiceRollActionNode";
import { DiceRollValues, DiceRollValuesProps } from "./DiceRollValues";
export interface DiceRollProps {
rollKey: string;
nextRollKey: string | null;
rollTotal: number | null;
diceRollRequest: RollRequest;
onChange?: (
key: string,
newValue: string | null,
prevValue: string | null,
nextRollKey: string | null,
rollTotal: number | null,
rollValues: DiceRollValuesProps["rollValues"]
) => void;
onRoll: (
key: string,
diceRollRequest: RollRequest,
nextRollKey: string | null
) => void;
rollValues: DiceRollValuesProps["rollValues"];
rollStatus: DiceRollActionNodeProps["rollStatus"];
options?: DiceRollActionNodeProps["options"];
assignedValue: DiceRollActionNodeProps["assignedValue"];
rollButtonText?: DiceRollActionNodeProps["rollButtonText"];
selectPlaceholderText?: DiceRollActionNodeProps["selectPlaceholderText"];
}
const DiceRoll: React.FunctionComponent<DiceRollProps> = ({
rollKey,
nextRollKey,
options,
rollTotal,
rollValues,
assignedValue,
diceRollRequest,
onChange,
onRoll,
rollStatus,
rollButtonText,
selectPlaceholderText,
}) => {
const handleChange = useCallback(
(changedValue: string): void => {
const prevValue = assignedValue;
const newValue: string | null = changedValue ?? null;
if (newValue !== prevValue) {
if (onChange) {
onChange(
rollKey,
newValue,
prevValue,
nextRollKey,
rollTotal,
rollValues
);
}
}
},
[onChange, rollKey, assignedValue, nextRollKey, rollTotal, rollValues]
);
const handleRollClick = useCallback((): void => {
onRoll(rollKey, diceRollRequest, nextRollKey);
}, [onRoll, rollKey, diceRollRequest, nextRollKey]);
let contentNode: React.ReactNode = null;
switch (rollStatus) {
case DataLoadingStatusEnum.LOADING:
contentNode = <LoadingPlaceholder label="Rolling" />;
break;
default: {
const diceTotalClassNames: Array<string> = ["ddbc-dice-roll__total"];
if (rollTotal === null) {
diceTotalClassNames.push("ddbc-dice-roll__total--empty");
}
contentNode = (
<React.Fragment>
<div className={diceTotalClassNames.join(" ")}>
<label className="ddbc-dice-roll__total-label" htmlFor={rollKey}>
{rollTotal ?? "--"}
</label>
</div>
<div className="ddbc-dice-roll__dice">
<DiceRollValues rollValues={rollValues} />
</div>
<DiceRollActionNode
rollKey={rollKey}
rollStatus={rollStatus}
rollTotal={rollTotal}
assignedValue={assignedValue}
options={options}
onRoll={handleRollClick}
onChange={handleChange}
rollButtonText={rollButtonText}
selectPlaceholderText={selectPlaceholderText}
/>
</React.Fragment>
);
break;
}
}
return <div className="ddbc-dice-roll">{contentNode}</div>;
};
export default React.memo(DiceRoll);
@@ -0,0 +1,53 @@
import React from "react";
import { HtmlSelectOption } from "@dndbeyond/character-rules-engine/es";
import { DataLoadingStatusEnum } from "../../../componentConstants";
import { Button } from "../../../legacy/Button";
import { Select } from "../../../legacy/Select";
export interface DiceRollActionNodeProps {
rollKey: string;
rollStatus: DataLoadingStatusEnum;
rollTotal: number | null;
assignedValue: string | null;
options?: Array<HtmlSelectOption>;
rollButtonText?: string;
selectPlaceholderText?: string;
onRoll: () => void;
onChange?: (newValue: string | null) => void;
}
const DiceRollActionNode: React.FunctionComponent<DiceRollActionNodeProps> = ({
rollKey,
rollStatus,
rollTotal,
assignedValue,
options,
onRoll,
onChange,
rollButtonText = "Roll",
selectPlaceholderText = "--",
}) => {
if (options?.length && rollTotal !== null) {
return (
<Select
id={rollKey}
value={assignedValue}
options={options}
placeholder={selectPlaceholderText}
onChange={onChange}
/>
);
}
return (
<Button
size="large"
// style='outline'
onClick={onRoll}
>
{rollButtonText}
</Button>
);
};
export default React.memo(DiceRollActionNode);
@@ -0,0 +1,64 @@
import { orderBy } from "lodash";
import React from "react";
import {
RollValueContract,
Constants,
} from "@dndbeyond/character-rules-engine/es";
//eventually use RollValueContract.dieType to display dice svgs
export interface DiceRollValuesProps {
rollValues: Array<RollValueContract>;
}
const DiceRollValues: React.FunctionComponent<DiceRollValuesProps> = ({
rollValues,
}) => {
if (rollValues.length === 0) {
return null;
}
const keptDiceValues = rollValues.filter(
(valueContract) =>
valueContract.excludeType === Constants.DiceRollExcludeTypeEnum.ALL
);
const discardedValues = rollValues.filter(
(valueContract) =>
valueContract.excludeType === Constants.DiceRollExcludeTypeEnum.NONE
);
const orderedKeptDiceValues = orderBy(
keptDiceValues,
(valueContract) => valueContract.value,
"desc"
);
const orderedDiscardedValues = orderBy(
discardedValues,
(valueContract) => valueContract.value,
"desc"
);
const classNames: Array<string> = ["ddbc-dice-roll__dice-value"];
return (
<React.Fragment>
{orderedKeptDiceValues.map((rollValue: RollValueContract, idx) => {
return (
<div key={idx} className={classNames.join(" ")}>
{rollValue.value}
</div>
);
})}
{orderedDiscardedValues.map((rollValue: RollValueContract, idx) => {
classNames.push("ddbc-dice-roll__dice-value--discarded");
return (
<div key={idx} className={classNames.join(" ")}>
{rollValue.value}
</div>
);
})}
</React.Fragment>
);
};
export default React.memo(DiceRollValues);
@@ -0,0 +1,289 @@
import React, { useCallback, useMemo } from "react";
import {
HelperUtils,
HtmlSelectOption,
RollResultContract,
RollValueContract,
} from "@dndbeyond/character-rules-engine/es";
import { Dice, RollRequest } from "@dndbeyond/dice";
import { DataLoadingStatusEnum } from "../componentConstants";
import { Button, RemoveButton } from "../legacy";
import { TypeScriptUtils, DiceComponentUtils } from "../utils";
import { DiceRoll, DiceRollProps } from "./DiceRoll";
export interface DiceRollGroupProps {
className: string;
componentKey: string;
diceRolls: Array<RollResultContract>;
exclusiveOptions?: boolean;
confirmButtonText?: string;
onConfirm?: (
groupKey: string,
rollResults: Array<RollResultContract>
) => void;
onUpdateGroup?: (
groupKey: string,
rollResults: Array<RollResultContract>
) => void;
onRollError?: (errorMessage: string) => void;
groupKey: string;
nextGroupKey: string | null;
showRemoveButton?: boolean;
onRemoveGroup?: (groupKey: string, nextGroupKey: string | null) => void;
onResetGroup?: (
groupKey: string,
rollResults: Array<RollResultContract>
) => void;
onUpdateDiceRoll: (
rollKey: string,
properties: Omit<Partial<RollResultContract>, "rollKey">,
nextRollKey: string | null
) => void;
onSetRollStatus: (rollKey: string, status: DataLoadingStatusEnum) => void;
rollStatusLookup: Record<string, DataLoadingStatusEnum>;
options?: DiceRollProps["options"];
diceRollRequest:
| DiceRollProps["diceRollRequest"]
| Array<DiceRollProps["diceRollRequest"]>;
}
const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
className = "",
componentKey,
diceRolls,
options,
confirmButtonText = "Confirm",
showRemoveButton = false,
exclusiveOptions = false,
diceRollRequest,
groupKey,
nextGroupKey,
onUpdateGroup,
onRemoveGroup,
onRollError,
onConfirm,
onResetGroup,
onSetRollStatus,
onUpdateDiceRoll,
rollStatusLookup,
}) => {
const classNames = useMemo<Array<string>>(
() => ["ddbc-dice-roll-group", className],
[className]
);
const areRollsEmpty = useMemo<boolean>(
() => !diceRolls.some((roll) => roll.rollTotal !== null),
[diceRolls]
);
const areScoresUnassigned = useMemo<boolean>(
() =>
!diceRolls.some(
(roll) => roll.assignedValue !== null && roll.assignedValue !== ""
),
[diceRolls]
);
const assignedValues = useMemo<Array<string>>(() => {
return diceRolls
.map((roll) => roll.assignedValue)
.filter(TypeScriptUtils.isNotNullOrUndefined);
}, [diceRolls]);
const generatedOptions = useMemo<Array<Array<HtmlSelectOption>>>(() => {
if (!options) {
return [];
}
return diceRolls.map((roll): Array<HtmlSelectOption> => {
if (!exclusiveOptions) {
return options;
}
return options.filter(
(option) =>
roll.assignedValue === option.value ||
!assignedValues.includes(option.value.toString())
);
});
}, [options, diceRolls, exclusiveOptions, assignedValues]);
const generatedRollRequests = useMemo<Array<RollRequest>>(() => {
let requests: Array<RollRequest> = [];
for (let i = 0; i < diceRolls.length; i++) {
if (!Array.isArray(diceRollRequest)) {
requests.push(diceRollRequest);
} else {
if (diceRollRequest[i]) {
requests.push(diceRollRequest[i]);
} else {
requests.push(diceRollRequest[0]);
}
}
}
return requests;
}, [diceRollRequest, diceRolls]);
const handleRoll = useCallback(
(
rollKey: string,
diceRollRequest: RollRequest,
nextRollKey: string | null
): void => {
onSetRollStatus(rollKey, DataLoadingStatusEnum.LOADING);
Dice.roll(diceRollRequest)
.then((result) => {
//RollResult | undefined - RollResult is not exported from Dice;
// only expect to have one single rollRequest.
const rollResult = result.rolls[0].result ?? null;
if (!rollResult) {
throw new Error("error in rollResult");
}
const rollValues =
DiceComponentUtils.generateRollValueContracts(result);
if (!rollValues) {
throw new Error("error in rollResult values");
}
//update status
onSetRollStatus(rollKey, DataLoadingStatusEnum.LOADED);
//update diceRoll
onUpdateDiceRoll(
rollKey,
{ rollValues, rollTotal: rollResult.total },
nextRollKey
);
})
.catch((error: Error) => {
//this could handle different errors types from dice package or thrown errors from result
onSetRollStatus(rollKey, DataLoadingStatusEnum.NOT_LOADED);
if (onRollError) {
onRollError(error.message);
}
console.log("roll error", error);
});
},
[]
);
const handleChange = useCallback(
(
rollKey: string,
newValue: string | null,
prevValue: string | null,
nextRollKey: string | null,
rollTotal: number | null,
rollValues: Array<RollValueContract>
): void => {
if (newValue === prevValue) {
return;
}
onUpdateDiceRoll(
rollKey,
{ assignedValue: newValue, rollTotal, rollValues },
nextRollKey
);
},
[]
);
const handleConfirm = useCallback((): void => {
if (onConfirm) {
return onConfirm(groupKey, diceRolls);
}
}, [onConfirm, groupKey, diceRolls]);
const handleResetGroup = useCallback((): void => {
if (onResetGroup) {
onResetGroup(groupKey, diceRolls);
}
diceRolls.forEach((roll) => {
onSetRollStatus(roll.rollKey, DataLoadingStatusEnum.NOT_INITIALIZED);
});
}, [onResetGroup, groupKey]);
const handleRemoveGroup = useCallback((): void => {
if (onRemoveGroup) {
onRemoveGroup(groupKey, nextGroupKey);
}
}, [onRemoveGroup, groupKey, nextGroupKey]);
return (
<div className={classNames.join(" ")}>
<div className="ddbc-dice-roll-group__actions">
{showRemoveButton && (
<RemoveButton
size="small"
onClick={handleRemoveGroup}
enableConfirm={true}
>
Delete group
</RemoveButton>
)}
</div>
<div className="ddbc-dice-roll-group__rolls">
{diceRolls.map((diceRoll, idx) => {
const diceRollRequest = generatedRollRequests[idx];
if (!diceRollRequest) {
return null;
}
const rollStatus = HelperUtils.lookupDataOrFallback(
rollStatusLookup,
diceRoll.rollKey,
DataLoadingStatusEnum.NOT_INITIALIZED
);
return (
<DiceRoll
key={diceRoll.rollKey}
rollKey={diceRoll.rollKey}
nextRollKey={diceRoll.nextRollKey}
rollTotal={diceRoll.rollTotal}
rollValues={diceRoll.rollValues}
assignedValue={diceRoll.assignedValue}
options={generatedOptions[idx]}
onChange={handleChange}
onRoll={handleRoll}
diceRollRequest={diceRollRequest}
rollStatus={rollStatus}
/>
);
})}
</div>
<div className="ddbc-dice-roll-group__actions">
<RemoveButton
size="medium"
style="filled"
onClick={handleResetGroup}
disabled={areRollsEmpty}
enableConfirm={true}
>
Reset Group
</RemoveButton>
{onConfirm && (
<Button
size="medium"
onClick={handleConfirm}
disabled={areScoresUnassigned}
>
{confirmButtonText}
</Button>
)}
</div>
</div>
);
};
export default React.memo(DiceRollGroup);