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,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);
|
||||
+53
@@ -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);
|
||||
+64
@@ -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);
|
||||
Reference in New Issue
Block a user