New source found from dndbeyond.com
This commit is contained in:
@@ -1,20 +1,18 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import {
|
||||
AbilityManager,
|
||||
CharacterPreferences,
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { DiceTools, IRollContext, RollType } from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import { RollTypes } from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
|
||||
import BoxBackground from "../BoxBackground";
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import { DigitalDiceWrapperProps } from "../Dice/DigitalDiceWrapper/DigitalDiceWrapper";
|
||||
import AbilityScoreBoxSvg from "../Svg/boxes/AbilityScoreBoxSvg";
|
||||
import { isNotNullOrUndefined } from "../utils/TypeScriptUtils";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -23,7 +21,7 @@ interface Props {
|
||||
theme: CharacterTheme;
|
||||
onClick?: (ability: AbilityManager) => void;
|
||||
diceEnabled?: boolean;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
}
|
||||
export default function AbilitySummary({
|
||||
className = "",
|
||||
@@ -44,9 +42,6 @@ export default function AbilitySummary({
|
||||
}
|
||||
};
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
useContext(GameLogContext);
|
||||
|
||||
let classNames: Array<string> = [className, "ddbc-ability-summary"];
|
||||
|
||||
let primaryNode: React.ReactNode;
|
||||
@@ -55,23 +50,25 @@ export default function AbilitySummary({
|
||||
const abilityStatKey = isManager ? ability.getStatKey() : 0;
|
||||
const abilityLabel = isManager ? ability.getLabel() : ability.name;
|
||||
const abilityName = isManager ? ability.getName() : ability.name;
|
||||
const abilityModifier = isManager ? ability.getModifier() : ability.modifier;
|
||||
const abilityModifier = Number(
|
||||
isManager ? ability.getModifier() ?? 0 : ability.modifier
|
||||
);
|
||||
const abilityTotalScore = isManager ? ability.getTotalScore() : ability.score;
|
||||
|
||||
const diceWrapperProps = {
|
||||
rollType: RollType.Check,
|
||||
rollAction: abilityName,
|
||||
diceEnabled: diceEnabled,
|
||||
diceNotation: DiceTools.CustomD20(abilityModifier ?? 0),
|
||||
themeColor: theme.themeColor,
|
||||
rollContext: rollContext,
|
||||
rollTargetOptions: !!messageTargetOptions?.entities
|
||||
? Object.values(messageTargetOptions.entities).filter(
|
||||
isNotNullOrUndefined
|
||||
)
|
||||
: undefined,
|
||||
rollTargetDefault :defaultMessageTargetOption,
|
||||
userId: Number(userId),
|
||||
const diceWrapperProps: DigitalDiceWrapperProps = {
|
||||
rollType: RollTypes.Check,
|
||||
action: abilityName,
|
||||
isDiceEnabled: diceEnabled,
|
||||
diceNotation: `1d20${
|
||||
abilityModifier > 0
|
||||
? `+${abilityModifier}`
|
||||
: abilityModifier !== 0
|
||||
? abilityModifier
|
||||
: ""
|
||||
}`,
|
||||
entityId: String(rollContext.entityId),
|
||||
entityType: String(rollContext.entityType),
|
||||
name: String(rollContext.name),
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -79,9 +76,7 @@ export default function AbilitySummary({
|
||||
Constants.PreferenceAbilityScoreDisplayTypeEnum.SCORES_TOP
|
||||
) {
|
||||
primaryNode = (
|
||||
<DigitalDiceWrapper
|
||||
{...diceWrapperProps}
|
||||
>
|
||||
<DigitalDiceWrapper {...diceWrapperProps}>
|
||||
{abilityTotalScore}
|
||||
</DigitalDiceWrapper>
|
||||
);
|
||||
@@ -96,14 +91,8 @@ export default function AbilitySummary({
|
||||
primaryNode = "--";
|
||||
} else {
|
||||
primaryNode = (
|
||||
<DigitalDiceWrapper
|
||||
{...diceWrapperProps}
|
||||
>
|
||||
<NumberDisplay
|
||||
number={abilityModifier}
|
||||
type="signed"
|
||||
size="large"
|
||||
/>
|
||||
<DigitalDiceWrapper {...diceWrapperProps}>
|
||||
<NumberDisplay number={abilityModifier} type="signed" size="large" />
|
||||
</DigitalDiceWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import * as React from "react";
|
||||
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import BoxBackground from "../BoxBackground";
|
||||
import ArmorClassBoxSvg from "../Svg/boxes/ArmorClassBoxSvg";
|
||||
|
||||
@@ -36,7 +37,7 @@ export default class ArmorClassBox extends React.PureComponent<Props> {
|
||||
return (
|
||||
<section className={classNames.join(" ")} onClick={this.handleClick}>
|
||||
<BoxBackground StyleComponent={ArmorClassBoxSvg} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Armor Class</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Armor Class</h2>
|
||||
<div
|
||||
className={`ddbc-armor-class-box__label ${
|
||||
theme.isDarkMode ? "ddbc-armor-class-box__label--dark-mode" : ""
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import {
|
||||
AbilityLookup,
|
||||
Action,
|
||||
@@ -6,33 +9,34 @@ import {
|
||||
Constants,
|
||||
DataOriginRefData,
|
||||
Item,
|
||||
ItemPlan,
|
||||
RuleData,
|
||||
Spell,
|
||||
SpellCasterInfo,
|
||||
WeaponSpellDamageGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import CombatActionAttack from "../CombatAttack/CombatActionAttack";
|
||||
import CombatItemAttack from "../CombatAttack/CombatItemAttack";
|
||||
import CombatSpellAttack from "../CombatAttack/CombatSpellAttack";
|
||||
import { useMemo } from "react";
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface AttackTableProps extends React.HTMLAttributes<HTMLElement> {
|
||||
theme: CharacterTheme;
|
||||
showNotes?: boolean;
|
||||
diceEnabled?: boolean;
|
||||
diceEnabled?: boolean;
|
||||
attacks: Array<Attack>;
|
||||
weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
|
||||
abilityLookup: AbilityLookup;
|
||||
ruleData: RuleData;
|
||||
spellCasterInfo: SpellCasterInfo;
|
||||
onAttackClick?: (attack: Attack) => void;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
};
|
||||
itemPlans: Array<ItemPlan>;
|
||||
}
|
||||
|
||||
export const AttackTable = ({
|
||||
theme,
|
||||
@@ -49,73 +53,79 @@ export const AttackTable = ({
|
||||
showNotes = true,
|
||||
diceEnabled = false,
|
||||
|
||||
itemPlans,
|
||||
...props
|
||||
}: AttackTableProps) => {
|
||||
const columnClassNameBase = useMemo(() => [
|
||||
styles.col,
|
||||
theme?.isDarkMode && styles.darkMode,
|
||||
], [theme]);
|
||||
const columnClassNameBase = useMemo(
|
||||
() => [styles.col, theme?.isDarkMode && styles.darkMode],
|
||||
[theme]
|
||||
);
|
||||
|
||||
const getColumnClass = (addtionalClass: string) => clsx([
|
||||
...columnClassNameBase,
|
||||
addtionalClass,
|
||||
]);
|
||||
const getColumnClass = (addtionalClass: string) =>
|
||||
clsx([...columnClassNameBase, addtionalClass]);
|
||||
|
||||
const renderedAttacks = useMemo(
|
||||
(): React.ReactNode => !!attacks.length
|
||||
? attacks.map((attack): React.ReactNode => {
|
||||
const commonCombatAttackProps = {
|
||||
key: attack.key,
|
||||
attack,
|
||||
ruleData,
|
||||
abilityLookup,
|
||||
onClick: onAttackClick,
|
||||
showNotes,
|
||||
diceEnabled,
|
||||
theme,
|
||||
rollContext,
|
||||
proficiencyBonus,
|
||||
className: styles.attack,
|
||||
};
|
||||
(): React.ReactNode =>
|
||||
!!attacks.length ? (
|
||||
attacks.map((attack): React.ReactNode => {
|
||||
const commonCombatAttackProps = {
|
||||
key: attack.key,
|
||||
attack,
|
||||
ruleData,
|
||||
abilityLookup,
|
||||
onClick: onAttackClick,
|
||||
showNotes,
|
||||
diceEnabled,
|
||||
theme,
|
||||
rollContext,
|
||||
proficiencyBonus,
|
||||
className: styles.attack,
|
||||
};
|
||||
|
||||
switch (attack.type) {
|
||||
case Constants.AttackSourceTypeEnum.ACTION:
|
||||
case Constants.AttackSourceTypeEnum.CUSTOM:
|
||||
return (
|
||||
<CombatActionAttack
|
||||
action={attack.data as Action}
|
||||
{...commonCombatAttackProps}
|
||||
/>
|
||||
);
|
||||
switch (attack.type) {
|
||||
case Constants.AttackSourceTypeEnum.ACTION:
|
||||
case Constants.AttackSourceTypeEnum.CUSTOM:
|
||||
return (
|
||||
<CombatActionAttack
|
||||
action={attack.data as Action}
|
||||
{...commonCombatAttackProps}
|
||||
key={commonCombatAttackProps.key}
|
||||
/>
|
||||
);
|
||||
|
||||
case Constants.AttackSourceTypeEnum.ITEM:
|
||||
return (
|
||||
<CombatItemAttack
|
||||
item={attack.data as Item}
|
||||
weaponSpellDamageGroups={weaponSpellDamageGroups}
|
||||
{...commonCombatAttackProps}
|
||||
/>
|
||||
);
|
||||
case Constants.AttackSourceTypeEnum.ITEM:
|
||||
return (
|
||||
<CombatItemAttack
|
||||
item={attack.data as Item}
|
||||
weaponSpellDamageGroups={weaponSpellDamageGroups}
|
||||
itemPlans={itemPlans}
|
||||
{...commonCombatAttackProps}
|
||||
key={commonCombatAttackProps.key}
|
||||
/>
|
||||
);
|
||||
|
||||
case Constants.AttackSourceTypeEnum.SPELL:
|
||||
return (
|
||||
<CombatSpellAttack
|
||||
spell={attack.data as Spell}
|
||||
spellCasterInfo={spellCasterInfo}
|
||||
dataOriginRefData={dataOriginRefData}
|
||||
{...commonCombatAttackProps}
|
||||
/>
|
||||
);
|
||||
case Constants.AttackSourceTypeEnum.SPELL:
|
||||
return (
|
||||
<CombatSpellAttack
|
||||
spell={attack.data as Spell}
|
||||
spellCasterInfo={spellCasterInfo}
|
||||
dataOriginRefData={dataOriginRefData}
|
||||
{...commonCombatAttackProps}
|
||||
key={commonCombatAttackProps.key}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})
|
||||
: (
|
||||
<div className={clsx([
|
||||
styles.default,
|
||||
theme?.isDarkMode && styles.darkMode,
|
||||
])}>
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})
|
||||
) : (
|
||||
<div
|
||||
className={clsx([
|
||||
styles.default,
|
||||
theme?.isDarkMode && styles.darkMode,
|
||||
])}
|
||||
>
|
||||
Equip weapons or add spells to see your attacks here.
|
||||
</div>
|
||||
),
|
||||
@@ -131,35 +141,22 @@ export const AttackTable = ({
|
||||
proficiencyBonus,
|
||||
weaponSpellDamageGroups,
|
||||
spellCasterInfo,
|
||||
dataOriginRefData
|
||||
dataOriginRefData,
|
||||
itemPlans,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
return (
|
||||
<div {...props}>
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={getColumnClass(styles.icon)} />
|
||||
<div className={getColumnClass(styles.name)}>
|
||||
Attack
|
||||
</div>
|
||||
<div className={getColumnClass(styles.range)}>
|
||||
Range
|
||||
</div>
|
||||
<div className={getColumnClass(styles.tohit)}>
|
||||
Hit / DC
|
||||
</div>
|
||||
<div className={getColumnClass(styles.damage)}>
|
||||
Damage
|
||||
</div>
|
||||
{showNotes && (
|
||||
<div className={getColumnClass(styles.notes)}>
|
||||
Notes
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div data-testid="attack-table-content">
|
||||
{renderedAttacks}
|
||||
</div>
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={getColumnClass(styles.icon)} />
|
||||
<div className={getColumnClass(styles.name)}>Attack</div>
|
||||
<div className={getColumnClass(styles.range)}>Range</div>
|
||||
<div className={getColumnClass(styles.tohit)}>Hit / DC</div>
|
||||
<div className={getColumnClass(styles.damage)}>Damage</div>
|
||||
{showNotes && <div className={getColumnClass(styles.notes)}>Notes</div>}
|
||||
</div>
|
||||
<div data-testid="attack-table-content">{renderedAttacks}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ export default class AttunementSlot extends React.PureComponent<Props, {}> {
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ddbc-attunement-slot__preview" style={previewStyles} />
|
||||
<div className="ddbc-attunement-slot__content">
|
||||
<div className="ddbc-attunement-slot__name">
|
||||
@@ -111,7 +111,7 @@ export default class AttunementSlot extends React.PureComponent<Props, {}> {
|
||||
</div>
|
||||
{this.renderMetaItems()}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -119,12 +119,12 @@ export default class AttunementSlot extends React.PureComponent<Props, {}> {
|
||||
const { isMobile } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ddbc-attunement-slot__preview" />
|
||||
<div className="ddbc-attunement-slot__content">
|
||||
Choose an Item from {isMobile ? "Below" : " the Right"}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import { ThemedPlayButtonSvg } from "@dndbeyond/character-components/es";
|
||||
@@ -8,23 +9,25 @@ import {
|
||||
CampaignUtils,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import D6 from "@dndbeyond/fontawesome-cache/svgs/regular/dice-d6.svg";
|
||||
import ListTimeline from "@dndbeyond/fontawesome-cache/svgs/light/list-timeline.svg";
|
||||
import { GameLogNotificationWrapper } from "@dndbeyond/game-log-components";
|
||||
|
||||
import { Link } from "~/components/Link";
|
||||
|
||||
import { useSidebar } from "../../../../contexts/Sidebar";
|
||||
import { PaneComponentEnum } from "../../../../subApps/sheet/components/Sidebar/types";
|
||||
import { appEnvSelectors } from "../../Shared/selectors";
|
||||
import { GameLogState } from "../../Shared/stores/typings";
|
||||
import { NavigationUtils } from "../../Shared/utils";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
campaign: CampaignDataContract;
|
||||
campaign: CampaignDataContract | null;
|
||||
onCampaignShow?: () => void;
|
||||
className?: string;
|
||||
gameLog?: GameLogState;
|
||||
theme: CharacterTheme;
|
||||
isDesktop?: boolean;
|
||||
}
|
||||
|
||||
const CampaignSummary: React.FC<Props> = ({
|
||||
@@ -33,10 +36,12 @@ const CampaignSummary: React.FC<Props> = ({
|
||||
className = "",
|
||||
gameLog,
|
||||
theme,
|
||||
isDesktop,
|
||||
}) => {
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
} = useSidebar();
|
||||
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
||||
|
||||
const handleClick = (evt: React.MouseEvent): void => {
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
@@ -54,55 +59,66 @@ const CampaignSummary: React.FC<Props> = ({
|
||||
paneHistoryStart(PaneComponentEnum.GAME_LOG);
|
||||
};
|
||||
|
||||
if (isReadonly) return null;
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.container)}>
|
||||
<div
|
||||
className={clsx(styles.campaignSummary)}
|
||||
onClick={handleClick}
|
||||
data-testid="campaign-summary"
|
||||
>
|
||||
<span className={clsx(styles.summaryLabel)}>Campaign:</span>
|
||||
<span className={clsx(styles.summaryName)}>
|
||||
{CampaignUtils.getName(campaign)}
|
||||
</span>
|
||||
</div>
|
||||
<Tooltip title="Launch Game" isDarkMode={theme.isDarkMode}>
|
||||
<Link
|
||||
href={NavigationUtils.getLaunchGameUrl(campaign)}
|
||||
target="maps"
|
||||
aria-label="Launch Game"
|
||||
>
|
||||
<div className={clsx(styles.campaignButtonGroup)}>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<ThemedPlayButtonSvg
|
||||
className={clsx([
|
||||
styles.campaignButtonIcon,
|
||||
styles.campaignPlayButtonIcon,
|
||||
])}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<GameLogNotificationWrapper
|
||||
themeColor={theme.themeColor}
|
||||
gameLogIsOpen={gameLog?.isOpen ?? false}
|
||||
notificationOnClick={handleGameLogClick}
|
||||
>
|
||||
<Tooltip title="Game Log" isDarkMode={theme.isDarkMode}>
|
||||
{campaign && (
|
||||
<>
|
||||
<div
|
||||
role="button"
|
||||
aria-roledescription="Game Log"
|
||||
className={clsx(styles.campaignButtonGroup)}
|
||||
onClick={handleGameLogClick}
|
||||
className={clsx(styles.campaignSummary)}
|
||||
onClick={handleClick}
|
||||
data-testid="campaign-summary"
|
||||
>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<D6 className={clsx(styles.campaignButtonIcon)} />
|
||||
</div>
|
||||
<span className={clsx(styles.summaryLabel)}>Campaign:</span>
|
||||
<span className={clsx(styles.summaryName)}>
|
||||
{CampaignUtils.getName(campaign)}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</GameLogNotificationWrapper>
|
||||
<Tooltip title="Launch Game" isDarkMode={theme.isDarkMode}>
|
||||
<Link
|
||||
href={NavigationUtils.getLaunchGameUrl(campaign)}
|
||||
target="_blank"
|
||||
aria-label="Launch Game"
|
||||
>
|
||||
<div className={clsx(styles.campaignButtonGroup)}>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<ThemedPlayButtonSvg
|
||||
className={clsx([
|
||||
styles.campaignButtonIcon,
|
||||
styles.campaignPlayButtonIcon,
|
||||
])}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{isDesktop && (
|
||||
<GameLogNotificationWrapper
|
||||
themeColor={theme.themeColor}
|
||||
gameLogIsOpen={gameLog?.isOpen ?? false}
|
||||
notificationOnClick={handleGameLogClick}
|
||||
>
|
||||
<Tooltip title="Game Log" isDarkMode={theme.isDarkMode}>
|
||||
<div
|
||||
role="button"
|
||||
aria-roledescription="Game Log"
|
||||
className={clsx([
|
||||
styles.campaignButtonGroup,
|
||||
styles.soloGameLogButton,
|
||||
])}
|
||||
onClick={handleGameLogClick}
|
||||
>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<ListTimeline className={clsx(styles.campaignButtonIcon)} />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</GameLogNotificationWrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import CampaignSummary from "./CampaignSummary";
|
||||
|
||||
export default CampaignSummary;
|
||||
export { CampaignSummary };
|
||||
@@ -1,40 +0,0 @@
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import { DefaultCharacterName as DefaultName } from "~/constants";
|
||||
|
||||
interface Props {
|
||||
defaultCharacterName?: string;
|
||||
isDead?: boolean;
|
||||
isFaceMenu?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function CharacterName({
|
||||
defaultCharacterName = DefaultName,
|
||||
isDead = false,
|
||||
isFaceMenu = false,
|
||||
name,
|
||||
}: Props) {
|
||||
let displayName: string = name ? name : defaultCharacterName;
|
||||
if (isDead) {
|
||||
displayName = `(Dead) ${displayName}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
style={{ fontFamily: "Roboto Condensed" }}
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
fontSize: isFaceMenu ? "24px" : { xs: "18px", sm: "24px" },
|
||||
lineHeight: 1.4,
|
||||
letterSpacing: "normal",
|
||||
}}
|
||||
>
|
||||
{displayName}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import CharacterName from "./CharacterName";
|
||||
|
||||
export default CharacterName;
|
||||
export { CharacterName };
|
||||
@@ -1,356 +0,0 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
Modal,
|
||||
Paper,
|
||||
Stack,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import clsx from "clsx";
|
||||
import { useContext } from "react";
|
||||
|
||||
import { RuleDataUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { SourceCategoryDescription } from "~/constants";
|
||||
import { useFeatureFlags } from "~/contexts/FeatureFlag";
|
||||
import { generateCharacterPreferences } from "~/helpers/generateCharacterPreferences";
|
||||
import PreferenceUpdateLocation from "~/tools/js/Shared/constants/PreferenceUpdateLocation";
|
||||
|
||||
import {
|
||||
CheckboxInfo,
|
||||
FormCheckBoxesField,
|
||||
} from "../../Shared/components/common/FormCheckBoxesField";
|
||||
import AbilitySummary from "../AbilitySummary";
|
||||
import PrivacyTypeRadio from "../PrivacyTypeRadio";
|
||||
import { UserPreferenceContext } from "../UserPreference";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface CharacterSettingsModalProps {
|
||||
open: boolean;
|
||||
updateLocation: PreferenceUpdateLocation;
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
const CharacterSettingsModal = ({
|
||||
open,
|
||||
updateLocation,
|
||||
handleClose,
|
||||
}: CharacterSettingsModalProps) => {
|
||||
const {
|
||||
privacyType,
|
||||
isDarkModeEnabled,
|
||||
abilityScoreDisplayType,
|
||||
updatePreferences,
|
||||
ruleData,
|
||||
defaultEnabledSourceCategories,
|
||||
isHomebrewEnabled,
|
||||
} = useContext(UserPreferenceContext);
|
||||
|
||||
const { featureFlags } = useFeatureFlags();
|
||||
|
||||
const preferences = generateCharacterPreferences(featureFlags);
|
||||
|
||||
const getTheme = (darkMode: boolean) => ({
|
||||
name: "DDB Red",
|
||||
backgroundColor: darkMode ? "#10161ADB" : "#FEFEFE",
|
||||
isDefault: true,
|
||||
themeColor: "#C53131",
|
||||
themeColorId: null,
|
||||
isDarkMode: darkMode || false,
|
||||
});
|
||||
|
||||
const rollContext = {
|
||||
entityId: "52962664",
|
||||
entityType: "character",
|
||||
name: "Stor Hornraven",
|
||||
avatarUrl:
|
||||
"https://stg.dndbeyond.com/avatars/18589/680/1581111423-52962664.jpeg?width=150&height=150&fit=crop&quality=95&auto=webp",
|
||||
};
|
||||
|
||||
const handleSourceCategoryChange = (
|
||||
sourceId: number,
|
||||
isActive: boolean
|
||||
): void => {
|
||||
const newSourceCategories = {
|
||||
...defaultEnabledSourceCategories,
|
||||
[sourceId]: isActive,
|
||||
};
|
||||
|
||||
updatePreferences({
|
||||
privacyType,
|
||||
isDarkModeEnabled,
|
||||
abilityScoreDisplayType,
|
||||
updateLocation,
|
||||
defaultEnabledSourceCategories: newSourceCategories,
|
||||
isHomebrewEnabled,
|
||||
});
|
||||
};
|
||||
|
||||
let sourceToggles: Array<CheckboxInfo> = [];
|
||||
let partneredSourceCheckboxes: Array<CheckboxInfo> = [];
|
||||
let allPartneredSources: Array<number> = [];
|
||||
|
||||
if (ruleData) {
|
||||
const activeSourceCategories = Object.keys(defaultEnabledSourceCategories)
|
||||
.filter((key) => defaultEnabledSourceCategories[key])
|
||||
.map(Number);
|
||||
RuleDataUtils.getSourceCategories(ruleData).forEach((sourceCategory) => {
|
||||
if (!sourceCategory.isToggleable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const checkbox: CheckboxInfo = {
|
||||
label: `${sourceCategory.name}`,
|
||||
initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
|
||||
onChange: handleSourceCategoryChange.bind(this, sourceCategory.id),
|
||||
sortOrder: sourceCategory.sortOrder,
|
||||
description: sourceCategory.description ?? "",
|
||||
};
|
||||
|
||||
if (sourceCategory.isPartneredContent) {
|
||||
delete checkbox.description;
|
||||
partneredSourceCheckboxes.push(checkbox);
|
||||
allPartneredSources.push(sourceCategory.id);
|
||||
} else {
|
||||
sourceToggles.push(checkbox);
|
||||
}
|
||||
});
|
||||
|
||||
//Add Homebrew to sources for display
|
||||
sourceToggles.push({
|
||||
label: "Homebrew",
|
||||
initiallyEnabled: isHomebrewEnabled,
|
||||
description: SourceCategoryDescription.homebrew,
|
||||
onChange: (isActive: boolean) => {
|
||||
updatePreferences({
|
||||
privacyType,
|
||||
isDarkModeEnabled,
|
||||
abilityScoreDisplayType,
|
||||
updateLocation,
|
||||
defaultEnabledSourceCategories,
|
||||
isHomebrewEnabled: isActive,
|
||||
});
|
||||
},
|
||||
|
||||
sortOrder: 0,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
className={clsx(styles.modal)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Paper className={clsx(styles.content)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
style={{ padding: 16, borderBottom: "1px solid #A2ACB2" }}
|
||||
>
|
||||
<SettingsOutlinedIcon
|
||||
style={{
|
||||
color: "rgba(0,0,0,0.54)",
|
||||
marginRight: 5,
|
||||
height: 22,
|
||||
width: 22,
|
||||
}}
|
||||
/>
|
||||
<label className={clsx(styles.modalTitle)}>
|
||||
Default Character Settings
|
||||
</label>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={handleClose}
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
color: "rgba(0,0,0,0.54)",
|
||||
}}
|
||||
>
|
||||
<CloseIcon style={{ width: 20 }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Box style={{ padding: 16 }}>
|
||||
<label className={clsx(styles.modalDescription)}>
|
||||
These settings apply defaults to all new Characters you create. Any
|
||||
settings you apply to a specific Character will override the choices
|
||||
you make here.
|
||||
</label>
|
||||
|
||||
<>
|
||||
<FormCheckBoxesField
|
||||
heading="Sources"
|
||||
description={SourceCategoryDescription.official}
|
||||
checkboxes={sourceToggles}
|
||||
showAccordion={false}
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Partnered Content"
|
||||
description={SourceCategoryDescription.partnered}
|
||||
checkboxes={partneredSourceCheckboxes}
|
||||
checkUncheckAllEnabled={true}
|
||||
showAccordion={false}
|
||||
accordionHeading="Choose Partners"
|
||||
allText="All"
|
||||
/>
|
||||
</>
|
||||
|
||||
<PrivacyTypeRadio
|
||||
color="info"
|
||||
initialValue={privacyType}
|
||||
handleChange={(e) =>
|
||||
updatePreferences({
|
||||
isDarkModeEnabled,
|
||||
abilityScoreDisplayType,
|
||||
updateLocation,
|
||||
privacyType: parseInt(e.target.value),
|
||||
defaultEnabledSourceCategories,
|
||||
isHomebrewEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
color: "#000",
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
letterSpacing: -0.5,
|
||||
textTransform: "uppercase",
|
||||
margin: "19px 0 0",
|
||||
padding: "10px 0",
|
||||
}}
|
||||
>
|
||||
Display
|
||||
</p>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={8}>
|
||||
<FormControl>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
color="info"
|
||||
checked={isDarkModeEnabled}
|
||||
onChange={(e) =>
|
||||
updatePreferences({
|
||||
privacyType,
|
||||
abilityScoreDisplayType,
|
||||
updateLocation,
|
||||
isDarkModeEnabled: e.target.checked,
|
||||
defaultEnabledSourceCategories,
|
||||
isHomebrewEnabled,
|
||||
})
|
||||
}
|
||||
style={{ paddingTop: 0 }}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<p style={{ margin: 0, fontSize: 14 }}>Underdark Mode</p>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "grey.500",
|
||||
margin: 0,
|
||||
fontSize: 12,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
Activate dark mode for Character Sheets.
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
sx={{ display: "flex", alignItems: "flex-start" }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl style={{ marginTop: 24 }}>
|
||||
<FormLabel>
|
||||
<p style={{ margin: 0, fontSize: 14, color: "#000" }}>
|
||||
Ability Scores / Modifiers
|
||||
</p>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "grey.500",
|
||||
margin: "0 0 16px",
|
||||
fontSize: 12,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
Reverse the display of Ability Scores and Modifiers.{" "}
|
||||
</Typography>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<ToggleButtonGroup
|
||||
color="info"
|
||||
value={abilityScoreDisplayType}
|
||||
exclusive
|
||||
onChange={(e, value) => {
|
||||
if (value !== null)
|
||||
updatePreferences({
|
||||
privacyType,
|
||||
isDarkModeEnabled,
|
||||
updateLocation,
|
||||
abilityScoreDisplayType: value,
|
||||
defaultEnabledSourceCategories,
|
||||
isHomebrewEnabled,
|
||||
});
|
||||
}}
|
||||
aria-label="Platform"
|
||||
>
|
||||
<ToggleButton value={2}>Modifiers Top</ToggleButton>
|
||||
<ToggleButton value={1}>Scores Top</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={4}
|
||||
display="flex"
|
||||
alignItems="flex-start"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "grey.50",
|
||||
border: "1px solid transparent",
|
||||
borderColor: "grey.200",
|
||||
borderRadius: 1,
|
||||
display: "inline-block",
|
||||
py: 1,
|
||||
px: 2,
|
||||
}}
|
||||
>
|
||||
<p style={{ textAlign: "center", marginBottom: 4 }}>Preview</p>
|
||||
<AbilitySummary
|
||||
ability={{ name: "Strength", score: 14, modifier: 2 }}
|
||||
preferences={{
|
||||
...preferences,
|
||||
abilityScoreDisplayType: abilityScoreDisplayType,
|
||||
}}
|
||||
theme={getTheme(isDarkModeEnabled || false)}
|
||||
onClick={(e) => e.getBaseScore()}
|
||||
rollContext={rollContext}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterSettingsModal;
|
||||
@@ -1,4 +0,0 @@
|
||||
import CharacterSettingsModal from "./CharacterSettingsModal";
|
||||
|
||||
export default CharacterSettingsModal;
|
||||
export { CharacterSettingsModal };
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
@@ -12,9 +13,9 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { DefaultCharacterName as DefaultName } from "~/constants";
|
||||
import { CharacterName } from "~/subApps/sheet/components/CharacterName";
|
||||
|
||||
import CharacterAvatar from "../CharacterAvatar";
|
||||
import CharacterName from "../CharacterName";
|
||||
import CharacterProgressionSummary from "../CharacterProgressionSummary";
|
||||
import CharacterSummary from "../CharacterSummary";
|
||||
|
||||
@@ -31,6 +32,7 @@ interface Props {
|
||||
isInteractive: boolean;
|
||||
defaultCharacterName: string;
|
||||
className: string;
|
||||
isVttView?: boolean;
|
||||
calloutNode?: React.ReactNode;
|
||||
}
|
||||
export default class CharacterTidbits extends React.PureComponent<Props, {}> {
|
||||
@@ -39,6 +41,7 @@ export default class CharacterTidbits extends React.PureComponent<Props, {}> {
|
||||
isInteractive: true,
|
||||
className: "",
|
||||
defaultCharacterName: DefaultName,
|
||||
isVttView: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -48,13 +51,12 @@ export default class CharacterTidbits extends React.PureComponent<Props, {}> {
|
||||
gender,
|
||||
species,
|
||||
decorationInfo,
|
||||
deathCause,
|
||||
preferences,
|
||||
ruleData,
|
||||
xpInfo,
|
||||
defaultCharacterName,
|
||||
isInteractive,
|
||||
className,
|
||||
isVttView,
|
||||
calloutNode,
|
||||
} = this.props;
|
||||
|
||||
@@ -73,11 +75,7 @@ export default class CharacterTidbits extends React.PureComponent<Props, {}> {
|
||||
</div>
|
||||
<div className="ddbc-character-tidbits__body">
|
||||
<div className="ddbc-character-tidbits__heading">
|
||||
<CharacterName
|
||||
name={name}
|
||||
isDead={deathCause !== Constants.DeathCauseEnum.NONE}
|
||||
defaultCharacterName={defaultCharacterName}
|
||||
/>
|
||||
{!isVttView && <CharacterName />}
|
||||
{isInteractive && calloutNode && (
|
||||
<div className="ddbc-character-tidbits__menu-callout">
|
||||
{calloutNode}
|
||||
@@ -92,7 +90,12 @@ export default class CharacterTidbits extends React.PureComponent<Props, {}> {
|
||||
/>
|
||||
</div>
|
||||
{xpInfo && ruleData && (
|
||||
<div className="ddbc-character-tidbits__progression">
|
||||
<div
|
||||
className={clsx([
|
||||
"ddbc-character-tidbits__progression",
|
||||
isVttView && "ddbc-character-tidbits__progression--vttView",
|
||||
])}
|
||||
>
|
||||
<CharacterProgressionSummary
|
||||
ruleData={ruleData}
|
||||
progressionType={preferences.progressionType}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { ReactNode, MouseEvent, PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
import {
|
||||
DarkChevronUpSvg,
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
} from "../Svg";
|
||||
import CollapsibleHeaderContent from "./CollapsibleHeaderContent";
|
||||
|
||||
interface Props {
|
||||
header: React.ReactNode;
|
||||
headerFooter?: React.ReactNode;
|
||||
interface Props extends PropsWithChildren {
|
||||
header: ReactNode;
|
||||
headerFooter?: ReactNode;
|
||||
className: string;
|
||||
initiallyCollapsed: boolean;
|
||||
collapsed?: boolean;
|
||||
@@ -24,7 +24,7 @@ interface State {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export class Collapsible extends React.PureComponent<Props, State> {
|
||||
export class Collapsible extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
initiallyCollapsed: true,
|
||||
className: "",
|
||||
@@ -83,7 +83,7 @@ export class Collapsible extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
handleClick = (evt: MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
const { onChangeHandler } = this.props;
|
||||
|
||||
@@ -114,7 +114,7 @@ export class Collapsible extends React.PureComponent<Props, State> {
|
||||
useBuilderStyles,
|
||||
} = this.props;
|
||||
|
||||
let headerContentNode: React.ReactNode;
|
||||
let headerContentNode: ReactNode;
|
||||
if (typeof header === "string") {
|
||||
headerContentNode = <CollapsibleHeaderContent heading={header} />;
|
||||
} else {
|
||||
@@ -135,7 +135,7 @@ export class Collapsible extends React.PureComponent<Props, State> {
|
||||
// not implements
|
||||
}
|
||||
|
||||
let chevronNode: React.ReactNode = <DisabledChevronDownSvg />;
|
||||
let chevronNode: ReactNode = <DisabledChevronDownSvg />;
|
||||
if (!isCollapsed) {
|
||||
chevronNode =
|
||||
isDarkMode && !useBuilderStyles ? (
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class CollapsibleHeading extends React.PureComponent<{}, {}> {
|
||||
export default class CollapsibleHeading extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ddbc-collapsible__heading">{this.props.children}</div>
|
||||
|
||||
+26
-39
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
AbilityLookup,
|
||||
Action,
|
||||
@@ -12,23 +13,23 @@ import {
|
||||
RuleData,
|
||||
RuleDataUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import {
|
||||
Dice,
|
||||
RollType,
|
||||
DiceEvent,
|
||||
RollKind,
|
||||
IRollContext,
|
||||
} from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import {
|
||||
RollKinds,
|
||||
RollTypes,
|
||||
} from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
|
||||
import ActionName from "../../ActionName";
|
||||
import Damage from "../../Damage";
|
||||
import { DigitalDiceWrapper } from "../../Dice";
|
||||
import { AttackTypeIcon } from "../../Icons";
|
||||
import NoteComponents from "../../NoteComponents";
|
||||
import { DiceComponentUtils } from "../../utils";
|
||||
import CombatAttack from "../CombatAttack";
|
||||
import { CombatActionAttackComponentRangeInfo } from "./CombatActionAttackTypings";
|
||||
import { DigitalDiceWrapper } from "../../Dice";
|
||||
import Damage from "../../Damage";
|
||||
|
||||
interface Props {
|
||||
attack: Attack;
|
||||
@@ -40,7 +41,7 @@ interface Props {
|
||||
className: string;
|
||||
diceEnabled: boolean;
|
||||
theme: CharacterTheme;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
proficiencyBonus: number;
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ interface State {
|
||||
isCriticalHit: boolean;
|
||||
}
|
||||
class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
private elementRef = React.createRef<React.ReactNode>();
|
||||
diceEventHandler: (eventData: any) => void;
|
||||
|
||||
static defaultProps = {
|
||||
@@ -64,17 +66,6 @@ class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
this.diceEventHandler = DiceComponentUtils.setupResetCritStateOnRoll(
|
||||
ActionUtils.getName(this.props.attack.data as Action),
|
||||
this
|
||||
);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
Dice.removeEventListener(DiceEvent.ROLL, this.diceEventHandler);
|
||||
};
|
||||
|
||||
handleClick = (): void => {
|
||||
const { onClick, attack } = this.props;
|
||||
|
||||
@@ -107,7 +98,7 @@ class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
|
||||
if (longRange) {
|
||||
rangeValueNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<span
|
||||
className={`ddbc-combat-attack__range-value-close ${
|
||||
theme.isDarkMode
|
||||
@@ -126,7 +117,7 @@ class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
>
|
||||
({longRange})
|
||||
</span>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
rangeLabel = "";
|
||||
} else {
|
||||
@@ -380,9 +371,6 @@ class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
rollContext,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
const { isCriticalHit } = this.state;
|
||||
|
||||
const proficiency = ActionUtils.isProficient(action);
|
||||
@@ -411,19 +399,18 @@ class CombatActionAttack extends React.PureComponent<Props, State> {
|
||||
? damage.value.toString()
|
||||
: DiceUtils.renderDice(damage.value)
|
||||
}
|
||||
rollType={RollType.Damage}
|
||||
rollAction={ActionUtils.getName(attack.data as Action)}
|
||||
rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Damage}
|
||||
action={ActionUtils.getName(attack.data as Action)}
|
||||
rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
rollCallback={() => {
|
||||
if (isCriticalHit) {
|
||||
this.setState({ isCriticalHit: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Damage
|
||||
type={damage.type ? damage.type.name : null}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
Action,
|
||||
ActionUtils,
|
||||
@@ -10,16 +11,17 @@ import {
|
||||
SpellUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { AttackSourceTypeEnum } from "@dndbeyond/character-rules-engine/es/engine/Character";
|
||||
import {
|
||||
DiceTools,
|
||||
RollType,
|
||||
RollRequest,
|
||||
IRollContext,
|
||||
} from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import { DiceComponentUtils } from "../utils";
|
||||
import { RollTypes } from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import {
|
||||
RollContext,
|
||||
RollDicePayload,
|
||||
} from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import { DiceComponentUtils } from "../utils";
|
||||
|
||||
interface Props {
|
||||
attack: Attack;
|
||||
@@ -36,7 +38,7 @@ interface Props {
|
||||
damage: React.ReactNode;
|
||||
notes?: React.ReactNode;
|
||||
theme: CharacterTheme;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
onClick?: (attack: Attack) => void;
|
||||
|
||||
showNotes: boolean;
|
||||
@@ -65,7 +67,7 @@ class CombatAttack extends React.PureComponent<Props, {}> {
|
||||
}
|
||||
};
|
||||
|
||||
handleRollResults = (result: RollRequest) => {
|
||||
handleRollResults = (result: RollDicePayload["data"]) => {
|
||||
const { onRoll } = this.props;
|
||||
|
||||
let wasCrit = DiceComponentUtils.isCriticalRoll(result);
|
||||
@@ -94,9 +96,6 @@ class CombatAttack extends React.PureComponent<Props, {}> {
|
||||
rollContext,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
let actionNode: React.ReactNode = null;
|
||||
if (toHit !== null) {
|
||||
let rollAction: string;
|
||||
@@ -116,26 +115,18 @@ class CombatAttack extends React.PureComponent<Props, {}> {
|
||||
actionNode = (
|
||||
<div className="ddbc-combat-attack__tohit">
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceTools.CustomD20(toHit)}
|
||||
rollType={RollType.ToHit}
|
||||
rollAction={rollAction}
|
||||
diceEnabled={diceEnabled}
|
||||
onRollResults={this.handleRollResults}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
diceNotation={`1d20${
|
||||
toHit > 0 ? `+${toHit}` : toHit !== 0 ? toHit : ""
|
||||
}`}
|
||||
rollType={RollTypes.ToHit}
|
||||
action={rollAction}
|
||||
isDiceEnabled={diceEnabled}
|
||||
rollCallback={this.handleRollResults}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
<NumberDisplay
|
||||
number={toHit}
|
||||
type="signed"
|
||||
isModified={false}
|
||||
/>
|
||||
<NumberDisplay number={toHit} type="signed" isModified={false} />
|
||||
</DigitalDiceWrapper>
|
||||
</div>
|
||||
);
|
||||
|
||||
+33
-51
@@ -8,20 +8,19 @@ import {
|
||||
DiceUtils,
|
||||
FormatUtils,
|
||||
Item,
|
||||
ItemPlan,
|
||||
ItemUtils,
|
||||
RuleData,
|
||||
RuleDataUtils,
|
||||
WeaponSpellDamageGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import {
|
||||
Dice,
|
||||
RollType,
|
||||
DiceEvent,
|
||||
RollKind,
|
||||
IRollContext,
|
||||
} from "@dndbeyond/dice";
|
||||
import StarIcon from "@dndbeyond/fontawesome-cache/svgs/solid/star.svg";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import {
|
||||
RollKinds,
|
||||
RollTypes,
|
||||
} from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { ItemName } from "~/components/ItemName";
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
@@ -45,14 +44,16 @@ interface Props {
|
||||
className: string;
|
||||
diceEnabled: boolean;
|
||||
theme: CharacterTheme;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
proficiencyBonus: number;
|
||||
itemPlans: Array<ItemPlan>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isCriticalHit: boolean;
|
||||
}
|
||||
class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
private elementRef = React.createRef<React.ReactNode>();
|
||||
diceEventHandler: (eventData: any) => void;
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -69,17 +70,6 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
diceEnabled: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.diceEventHandler = DiceComponentUtils.setupResetCritStateOnRoll(
|
||||
ItemUtils.getName(this.props.attack.data as Item),
|
||||
this
|
||||
);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
Dice.removeEventListener(DiceEvent.ROLL, this.diceEventHandler);
|
||||
};
|
||||
|
||||
handleClick = (): void => {
|
||||
const { onClick, attack } = this.props;
|
||||
|
||||
@@ -97,6 +87,7 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
showNotes,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
itemPlans,
|
||||
} = this.props;
|
||||
|
||||
if (!showNotes) {
|
||||
@@ -111,7 +102,8 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
weaponSpellDamageGroups,
|
||||
ruleData,
|
||||
abilityLookup,
|
||||
proficiencyBonus
|
||||
proficiencyBonus,
|
||||
ItemUtils.getItemPlan(item, itemPlans)
|
||||
)}
|
||||
theme={theme}
|
||||
/>
|
||||
@@ -136,9 +128,6 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
ruleData,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
const { isCriticalHit } = this.state;
|
||||
|
||||
const toHit = ItemUtils.getToHit(item);
|
||||
@@ -219,19 +208,13 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
versatileDamageNode = (
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceUtils.renderDice(versatileDamage)}
|
||||
rollType={RollType.Damage}
|
||||
rollAction={ItemUtils.getName(attack.data as Item)}
|
||||
rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Damage}
|
||||
action={ItemUtils.getName(attack.data as Item)}
|
||||
rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
<Damage
|
||||
damage={DiceComponentUtils.getDamageDiceNotation(
|
||||
@@ -261,19 +244,18 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
? damage.toString()
|
||||
: DiceUtils.renderDice(damage)
|
||||
}
|
||||
rollType={RollType.Damage}
|
||||
rollAction={ItemUtils.getName(attack.data as Item)}
|
||||
rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Damage}
|
||||
action={ItemUtils.getName(attack.data as Item)}
|
||||
rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
rollCallback={() => {
|
||||
if (isCriticalHit) {
|
||||
this.setState({ isCriticalHit: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Damage
|
||||
damage={DiceComponentUtils.getDamageDiceNotation(
|
||||
@@ -303,7 +285,7 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
let rangeLabel: React.ReactNode;
|
||||
if (showRange) {
|
||||
rangeValue = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<span
|
||||
className={`ddbc-combat-attack__range-value-close ${
|
||||
theme.isDarkMode
|
||||
@@ -324,7 +306,7 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
|
||||
({longRange})
|
||||
</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
rangeLabel = "";
|
||||
} else {
|
||||
|
||||
+12
-14
@@ -13,7 +13,7 @@ import {
|
||||
RuleData,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { Dice, DiceEvent, IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { SpellName } from "~/components/SpellName";
|
||||
@@ -36,7 +36,7 @@ interface Props {
|
||||
className: string;
|
||||
diceEnabled: boolean;
|
||||
theme: CharacterTheme;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export default class CombatSpellAttack extends React.PureComponent<
|
||||
Props,
|
||||
State
|
||||
> {
|
||||
private elementRef = React.createRef<React.ReactNode>();
|
||||
diceEventHandler: (eventData: any) => void;
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -65,17 +66,6 @@ export default class CombatSpellAttack extends React.PureComponent<
|
||||
diceEnabled: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.diceEventHandler = DiceComponentUtils.setupResetCritStateOnRoll(
|
||||
SpellUtils.getName(this.props.spell),
|
||||
this
|
||||
);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
Dice.removeEventListener(DiceEvent.ROLL, this.diceEventHandler);
|
||||
};
|
||||
|
||||
handleClick = (): void => {
|
||||
const { onClick, attack } = this.props;
|
||||
|
||||
@@ -220,7 +210,10 @@ export default class CombatSpellAttack extends React.PureComponent<
|
||||
saveDcValue = attackSaveValue;
|
||||
saveDcLabel = SpellUtils.getSaveDcAbilityShortName(spell, ruleData);
|
||||
}
|
||||
let attackClassNames: Array<string> = ["ddbc-combat-attack--spell", className];
|
||||
let attackClassNames: Array<string> = [
|
||||
"ddbc-combat-attack--spell",
|
||||
className,
|
||||
];
|
||||
|
||||
if (isCriticalHit) {
|
||||
attackClassNames.push("ddbc-combat-attack--crit");
|
||||
@@ -263,6 +256,11 @@ export default class CombatSpellAttack extends React.PureComponent<
|
||||
theme={theme}
|
||||
isCriticalHit={isCriticalHit}
|
||||
rollContext={rollContext}
|
||||
rollCallback={() => {
|
||||
if (isCriticalHit) {
|
||||
this.setState({ isCriticalHit: false });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
onClick={this.handleClick}
|
||||
|
||||
@@ -121,7 +121,7 @@ export default class CreatureListRow extends React.PureComponent<Props> {
|
||||
let contentNode: React.ReactNode;
|
||||
if (movementInfo) {
|
||||
contentNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ddbc-creature-list__row-speed-value">
|
||||
<NumberDisplay type="distanceInFt" number={movementInfo.speed} />
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@ export default class CreatureListRow extends React.PureComponent<Props> {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
contentNode = (
|
||||
|
||||
@@ -1,36 +1,79 @@
|
||||
import { RollType } from "@dndbeyond/dice";
|
||||
import { WithDiceRollv2 } from "@dndbeyond/dice-components";
|
||||
import { RollableComponentProps } from "@dndbeyond/dice-components/dist/types/src/components/WithDiceRollv2";
|
||||
import { useMemo } from "react";
|
||||
import clsx from "clsx";
|
||||
import { FC, useCallback, useContext, useEffect, useState } from "react";
|
||||
|
||||
export interface DigitalDiceWrapperProps extends
|
||||
Omit<RollableComponentProps, "BaseComponent" | "useCustomizableDiceNotation">
|
||||
{
|
||||
import {
|
||||
GameLogContext,
|
||||
isMessage,
|
||||
useAwaitMessageBroker,
|
||||
} from "@dndbeyond/game-log-components";
|
||||
import type { AnyMessage } from "@dndbeyond/message-broker-lib";
|
||||
import { Rollable } from "@dndbeyond/pocket-dimension-dice/components/Rollable";
|
||||
import type { RollableProps } from "@dndbeyond/pocket-dimension-dice/components/Rollable";
|
||||
import "@dndbeyond/pocket-dimension-dice/components/Rollable/index.css";
|
||||
import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
export interface DigitalDiceWrapperProps
|
||||
extends Omit<RollableProps, "dmId" | "gameId"> {
|
||||
rollCallback?: (rollData: RollDicePayload["data"]) => void;
|
||||
isDiceEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const DigitalDiceWrapper: React.FC<DigitalDiceWrapperProps> = ({
|
||||
useAdvancedMenu = true,
|
||||
export const DigitalDiceWrapper: FC<DigitalDiceWrapperProps> = ({
|
||||
isContextMenuEnabled = true,
|
||||
rollType,
|
||||
children,
|
||||
...props
|
||||
diceNotation,
|
||||
isDiceEnabled = true,
|
||||
action,
|
||||
rollKind,
|
||||
entityId,
|
||||
entityType,
|
||||
name,
|
||||
rollCallback,
|
||||
}) => {
|
||||
const useCustomizableDiceNotation = useMemo(
|
||||
() => rollType === RollType.Damage
|
||||
|| rollType === RollType.Heal,
|
||||
[rollType]
|
||||
const [{ activeCampaign }] = useContext(GameLogContext);
|
||||
const [rollData, setRollData] = useState<RollDicePayload["data"]>();
|
||||
const mb = useAwaitMessageBroker();
|
||||
const handleMessage = useCallback(
|
||||
(message: AnyMessage) => {
|
||||
if (
|
||||
isMessage(message) &&
|
||||
message.eventType === "dice/roll/fulfilled" &&
|
||||
message.data.rollId === rollData?.rollId
|
||||
) {
|
||||
rollCallback?.(rollData!);
|
||||
setRollData(undefined);
|
||||
}
|
||||
},
|
||||
[rollCallback, rollData]
|
||||
);
|
||||
|
||||
// Only subscribe when there's a pending roll to listen for
|
||||
useEffect(() => {
|
||||
if (!mb || !rollCallback || !rollData) {
|
||||
return;
|
||||
}
|
||||
|
||||
return mb.subscribe(handleMessage);
|
||||
}, [mb, rollCallback, rollData, handleMessage]);
|
||||
|
||||
return (
|
||||
<WithDiceRollv2
|
||||
BaseComponent={(
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
useAdvancedMenu={useAdvancedMenu}
|
||||
useCustomizableDiceNotation={useCustomizableDiceNotation}
|
||||
rollType={rollType}
|
||||
{...props}
|
||||
/>
|
||||
<Rollable
|
||||
className={clsx(isDiceEnabled && "integrated-dice__container")}
|
||||
diceNotation={String(diceNotation)}
|
||||
action={action}
|
||||
entityId={entityId}
|
||||
entityType={entityType}
|
||||
name={name}
|
||||
dmId={activeCampaign ? String(activeCampaign.dmId) : undefined}
|
||||
gameId={activeCampaign ? String(activeCampaign.id) : undefined}
|
||||
rollKind={rollKind}
|
||||
rollType={rollType}
|
||||
isContextMenuEnabled={isContextMenuEnabled}
|
||||
isDiceEnabled={isDiceEnabled}
|
||||
setRollData={setRollData}
|
||||
>
|
||||
{children}
|
||||
</Rollable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { RollRequest } from "@dndbeyond/dice";
|
||||
import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import LoadingPlaceholder from "../../LoadingPlaceholder";
|
||||
import { DataLoadingStatusEnum } from "../../componentConstants";
|
||||
@@ -14,7 +14,7 @@ export interface DiceRollProps {
|
||||
rollKey: string;
|
||||
nextRollKey: string | null;
|
||||
rollTotal: number | null;
|
||||
diceRollRequest: RollRequest;
|
||||
diceRollRequest: RollDicePayload["data"];
|
||||
onChange?: (
|
||||
key: string,
|
||||
newValue: string | null,
|
||||
@@ -25,7 +25,7 @@ export interface DiceRollProps {
|
||||
) => void;
|
||||
onRoll: (
|
||||
key: string,
|
||||
diceRollRequest: RollRequest,
|
||||
diceRollRequest: RollDicePayload["data"],
|
||||
nextRollKey: string | null
|
||||
) => void;
|
||||
rollValues: DiceRollValuesProps["rollValues"];
|
||||
@@ -87,7 +87,7 @@ const DiceRoll: React.FunctionComponent<DiceRollProps> = ({
|
||||
}
|
||||
|
||||
contentNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className={diceTotalClassNames.join(" ")}>
|
||||
<label className="ddbc-dice-roll__total-label" htmlFor={rollKey}>
|
||||
{rollTotal ?? "--"}
|
||||
@@ -107,7 +107,7 @@ const DiceRoll: React.FunctionComponent<DiceRollProps> = ({
|
||||
rollButtonText={rollButtonText}
|
||||
selectPlaceholderText={selectPlaceholderText}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,9 +2,11 @@ import React from "react";
|
||||
|
||||
import { HtmlSelectOption } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
|
||||
import { DataLoadingStatusEnum } from "../../../componentConstants";
|
||||
import { Button } from "../../../legacy/Button";
|
||||
import { Select } from "../../../legacy/Select";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export interface DiceRollActionNodeProps {
|
||||
rollKey: string;
|
||||
@@ -15,7 +17,7 @@ export interface DiceRollActionNodeProps {
|
||||
rollButtonText?: string;
|
||||
selectPlaceholderText?: string;
|
||||
onRoll: () => void;
|
||||
onChange?: (newValue: string | null) => void;
|
||||
onChange?: (newValue: string | number) => void;
|
||||
}
|
||||
const DiceRollActionNode: React.FunctionComponent<DiceRollActionNodeProps> = ({
|
||||
rollKey,
|
||||
@@ -31,11 +33,13 @@ const DiceRollActionNode: React.FunctionComponent<DiceRollActionNodeProps> = ({
|
||||
if (options?.length && rollTotal !== null) {
|
||||
return (
|
||||
<Select
|
||||
className={styles.select}
|
||||
id={rollKey}
|
||||
value={assignedValue}
|
||||
options={options}
|
||||
placeholder={selectPlaceholderText}
|
||||
onChange={onChange}
|
||||
name={rollKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import DiceRollActionNode from "./DiceRollActionNode";
|
||||
|
||||
//Props typings
|
||||
export * from "./DiceRollActionNode";
|
||||
|
||||
export { DiceRollActionNode };
|
||||
|
||||
export default DiceRollActionNode;
|
||||
+2
-2
@@ -40,7 +40,7 @@ const DiceRollValues: React.FunctionComponent<DiceRollValuesProps> = ({
|
||||
const classNames: Array<string> = ["ddbc-dice-roll__dice-value"];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{orderedKeptDiceValues.map((rollValue: RollValueContract, idx) => {
|
||||
return (
|
||||
<div key={idx} className={classNames.join(" ")}>
|
||||
@@ -57,7 +57,7 @@ const DiceRollValues: React.FunctionComponent<DiceRollValuesProps> = ({
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import DiceRollValues from "./DiceRollValues";
|
||||
|
||||
//Props typings
|
||||
export * from "./DiceRollValues";
|
||||
|
||||
export { DiceRollValues };
|
||||
|
||||
export default DiceRollValues;
|
||||
@@ -0,0 +1,10 @@
|
||||
import DiceRoll from "./DiceRoll";
|
||||
|
||||
//Props typings
|
||||
export * from "./DiceRoll";
|
||||
export * from "./DiceRollValues";
|
||||
export * from "./DiceRollActionNode";
|
||||
|
||||
export { DiceRoll };
|
||||
|
||||
export default DiceRoll;
|
||||
@@ -1,12 +1,27 @@
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import React, { useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
CampaignUtils,
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
RollResultContract,
|
||||
RollValueContract,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { Dice, RollRequest } from "@dndbeyond/dice";
|
||||
import { Platform } from "@dndbeyond/event-pipeline-lib/shared";
|
||||
import {
|
||||
GameLogContext,
|
||||
isMessage,
|
||||
useMessageBroker,
|
||||
} from "@dndbeyond/game-log-components";
|
||||
import { DiceVisibilities } from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { Context as DiceWorkerContext } from "@dndbeyond/pocket-dimension-dice/context";
|
||||
import { instrumentDiceRoll } from "@dndbeyond/pocket-dimension-dice/helpers/bi/instrumentDiceRoll";
|
||||
import { rollDice } from "@dndbeyond/pocket-dimension-dice/helpers/rollDice";
|
||||
import { useUserDiceDataStore } from "@dndbeyond/pocket-dimension-dice/hooks/useUserDiceDataStore";
|
||||
import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { useAuth } from "~/contexts/Authentication";
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
|
||||
import { DataLoadingStatusEnum } from "../componentConstants";
|
||||
import { Button, RemoveButton } from "../legacy";
|
||||
@@ -44,9 +59,7 @@ export interface DiceRollGroupProps {
|
||||
onSetRollStatus: (rollKey: string, status: DataLoadingStatusEnum) => void;
|
||||
rollStatusLookup: Record<string, DataLoadingStatusEnum>;
|
||||
options?: DiceRollProps["options"];
|
||||
diceRollRequest:
|
||||
| DiceRollProps["diceRollRequest"]
|
||||
| Array<DiceRollProps["diceRollRequest"]>;
|
||||
diceRollRequest: RollDicePayload["data"] | Array<RollDicePayload["data"]>;
|
||||
}
|
||||
const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
|
||||
className = "",
|
||||
@@ -68,6 +81,24 @@ const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
|
||||
onUpdateDiceRoll,
|
||||
rollStatusLookup,
|
||||
}) => {
|
||||
const { env, hasPddErrored, isPddReady } = useContext(DiceWorkerContext);
|
||||
const [{ userId }] = useContext(GameLogContext);
|
||||
const { campaign } = useCharacterEngine();
|
||||
const gameId = campaign ? String(campaign.id) : undefined;
|
||||
const dmId = campaign
|
||||
? String(CampaignUtils.getDmUserId(campaign))
|
||||
: undefined;
|
||||
const [groupRolls, setGroupRolls] = useState<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
rollKey: string;
|
||||
properties: Partial<RollResultContract>;
|
||||
nextRollKey: string | null;
|
||||
}
|
||||
>
|
||||
>({});
|
||||
const user = useAuth();
|
||||
const classNames = useMemo<Array<string>>(
|
||||
() => ["ddbc-dice-roll-group", className],
|
||||
[className]
|
||||
@@ -110,8 +141,8 @@ const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
|
||||
});
|
||||
}, [options, diceRolls, exclusiveOptions, assignedValues]);
|
||||
|
||||
const generatedRollRequests = useMemo<Array<RollRequest>>(() => {
|
||||
let requests: Array<RollRequest> = [];
|
||||
const generatedRollRequests = useMemo<Array<RollDicePayload["data"]>>(() => {
|
||||
let requests: Array<RollDicePayload["data"]> = [];
|
||||
for (let i = 0; i < diceRolls.length; i++) {
|
||||
if (!Array.isArray(diceRollRequest)) {
|
||||
requests.push(diceRollRequest);
|
||||
@@ -130,50 +161,94 @@ const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
|
||||
const handleRoll = useCallback(
|
||||
(
|
||||
rollKey: string,
|
||||
diceRollRequest: RollRequest,
|
||||
diceRollRequest: RollDicePayload["data"],
|
||||
nextRollKey: string | null
|
||||
): void => {
|
||||
onSetRollStatus(rollKey, DataLoadingStatusEnum.LOADING);
|
||||
const diceNotation = `${diceRollRequest.rolls[0].diceNotation.set
|
||||
.map((set) => `${set?.count}${set?.dieType}`)
|
||||
.join("")}`;
|
||||
const { data } = rollDice(
|
||||
{
|
||||
action: diceRollRequest.action,
|
||||
diceNotation,
|
||||
entityId: String(diceRollRequest.context?.entityId),
|
||||
entityType: String(diceRollRequest.context?.entityType),
|
||||
gameId,
|
||||
rollType: diceRollRequest.rolls[0].rollType,
|
||||
setId: String(
|
||||
useUserDiceDataStore.getState().getUserDiceData(Number(userId))
|
||||
?.setId
|
||||
),
|
||||
userId,
|
||||
avatarUrl: diceRollRequest.context?.avatarUrl,
|
||||
name: diceRollRequest.context?.name,
|
||||
operand: diceRollRequest.rolls[0].diceNotation.set[0]?.operand,
|
||||
dmId,
|
||||
},
|
||||
hasPddErrored,
|
||||
isPddReady,
|
||||
(eventPayload) =>
|
||||
instrumentDiceRoll(
|
||||
user,
|
||||
eventPayload,
|
||||
Platform.Web,
|
||||
"characterBuilderRollGroup",
|
||||
campaign?.id,
|
||||
undefined,
|
||||
env
|
||||
)
|
||||
);
|
||||
|
||||
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;
|
||||
const rollValues = DiceComponentUtils.generateRollValueContracts(data);
|
||||
|
||||
if (!rollResult) {
|
||||
throw new Error("error in rollResult");
|
||||
}
|
||||
if (!rollValues) {
|
||||
throw new Error("error in rollResult values");
|
||||
}
|
||||
|
||||
const rollValues =
|
||||
DiceComponentUtils.generateRollValueContracts(result);
|
||||
const newGroupRoll = {
|
||||
rollKey,
|
||||
properties: {
|
||||
rollValues,
|
||||
rollTotal: data.rolls[0].result?.total,
|
||||
},
|
||||
nextRollKey,
|
||||
};
|
||||
|
||||
if (!rollValues) {
|
||||
throw new Error("error in rollResult values");
|
||||
}
|
||||
setGroupRolls((prevState) => ({
|
||||
...prevState,
|
||||
[String(data?.rollId)]: newGroupRoll,
|
||||
}));
|
||||
|
||||
//update status
|
||||
onSetRollStatus(rollKey, DataLoadingStatusEnum.LOADED);
|
||||
if (
|
||||
data.rollId &&
|
||||
useUserDiceDataStore.getState().getUserDiceData(Number(userId))
|
||||
?.settings.visibility === DiceVisibilities.Disabled
|
||||
) {
|
||||
//update status
|
||||
onSetRollStatus(newGroupRoll.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);
|
||||
});
|
||||
//update diceRoll
|
||||
onUpdateDiceRoll(
|
||||
newGroupRoll.rollKey,
|
||||
newGroupRoll.properties,
|
||||
newGroupRoll.nextRollKey
|
||||
);
|
||||
} else {
|
||||
onSetRollStatus(rollKey, DataLoadingStatusEnum.LOADING);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[
|
||||
campaign?.id,
|
||||
dmId,
|
||||
env,
|
||||
gameId,
|
||||
hasPddErrored,
|
||||
isPddReady,
|
||||
onSetRollStatus,
|
||||
onUpdateDiceRoll,
|
||||
user,
|
||||
userId,
|
||||
]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@@ -220,6 +295,32 @@ const DiceRollGroup: React.FunctionComponent<DiceRollGroupProps> = ({
|
||||
}
|
||||
}, [onRemoveGroup, groupKey, nextGroupKey]);
|
||||
|
||||
useMessageBroker(
|
||||
useCallback(
|
||||
(message) => {
|
||||
if (
|
||||
isMessage(message) &&
|
||||
message.eventType === "dice/roll/fulfilled" &&
|
||||
groupRolls[message.data.rollId]
|
||||
) {
|
||||
//update status
|
||||
onSetRollStatus(
|
||||
groupRolls[message.data.rollId].rollKey,
|
||||
DataLoadingStatusEnum.LOADED
|
||||
);
|
||||
|
||||
//update diceRoll
|
||||
onUpdateDiceRoll(
|
||||
groupRolls[message.data.rollId].rollKey,
|
||||
groupRolls[message.data.rollId].properties,
|
||||
groupRolls[message.data.rollId].nextRollKey
|
||||
);
|
||||
}
|
||||
},
|
||||
[groupRolls, onSetRollStatus, onUpdateDiceRoll]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")}>
|
||||
<div className="ddbc-dice-roll-group__actions">
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import DiceRoll from "./DiceRoll";
|
||||
import DiceRollGroup from "./DiceRollGroup";
|
||||
|
||||
export * from "./DiceRollGroup";
|
||||
export { DiceRollGroup, DiceRoll };
|
||||
|
||||
export default DiceRollGroup;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { RollGroupContract } from "@dndbeyond/character-rules-engine/es";
|
||||
import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { DiceRollGroup, DiceRollGroupProps } from "../DiceRollGroup";
|
||||
import { Button } from "../legacy/Button";
|
||||
@@ -12,7 +13,7 @@ export interface DiceRollGroupManagerProps {
|
||||
componentKey: DiceRollGroupProps["componentKey"];
|
||||
options?: DiceRollGroupProps["options"];
|
||||
exclusiveOptions?: DiceRollGroupProps["exclusiveOptions"];
|
||||
diceRollRequest: DiceRollGroupProps["diceRollRequest"];
|
||||
diceRollRequest: RollDicePayload["data"];
|
||||
onConfirm?: DiceRollGroupProps["onConfirm"];
|
||||
confirmButtonText?: DiceRollGroupProps["confirmButtonText"];
|
||||
onRollError?: DiceRollGroupProps["onRollError"];
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import AoeTypeIcon from "./AoeTypeIcon";
|
||||
|
||||
export default AoeTypeIcon;
|
||||
export { AoeTypeIcon };
|
||||
@@ -0,0 +1,4 @@
|
||||
import AttackTypeIcon from "./AttackTypeIcon";
|
||||
|
||||
export default AttackTypeIcon;
|
||||
export { AttackTypeIcon };
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { PureComponent, MouseEvent, ReactNode, PropsWithChildren } from "react";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
className: string;
|
||||
tooltip: string;
|
||||
onClick?: () => void;
|
||||
@@ -12,7 +12,7 @@ interface Props {
|
||||
showIcon: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class ManageIcon extends React.PureComponent<Props> {
|
||||
export default class ManageIcon extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
tooltip: "Manage",
|
||||
enableTooltip: true,
|
||||
@@ -21,7 +21,7 @@ export default class ManageIcon extends React.PureComponent<Props> {
|
||||
className: "",
|
||||
};
|
||||
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
handleClick = (evt: MouseEvent): void => {
|
||||
const { onClick } = this.props;
|
||||
|
||||
if (onClick) {
|
||||
@@ -54,8 +54,8 @@ export default class ManageIcon extends React.PureComponent<Props> {
|
||||
classNames.push("ddbc-manage-icon--dark-mode");
|
||||
}
|
||||
|
||||
let contentNode: React.ReactNode = (
|
||||
<React.Fragment>
|
||||
let contentNode: ReactNode = (
|
||||
<>
|
||||
{children && (
|
||||
<span className="ddbc-manage-icon__content">{children}</span>
|
||||
)}
|
||||
@@ -66,7 +66,7 @@ export default class ManageIcon extends React.PureComponent<Props> {
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
|
||||
if (enableTooltip) {
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
AccessUtils,
|
||||
FormatUtils,
|
||||
Infusion,
|
||||
InfusionChoiceLookup,
|
||||
InfusionUtils,
|
||||
RuleData,
|
||||
RuleDataUtils,
|
||||
SnippetData,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import MarketplaceCta from "../MarketplaceCta";
|
||||
import Snippet from "../Snippet";
|
||||
|
||||
interface Props {
|
||||
infusion: Infusion;
|
||||
infusionChoiceLookup: InfusionChoiceLookup;
|
||||
snippetData: SnippetData;
|
||||
ruleData: RuleData;
|
||||
onClick?: (infusion: Infusion) => void;
|
||||
proficiencyBonus: number;
|
||||
}
|
||||
class InfusionPreview extends React.PureComponent<Props> {
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
const { onClick, infusion } = this.props;
|
||||
|
||||
if (onClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onClick(infusion);
|
||||
}
|
||||
};
|
||||
|
||||
renderDescription = (): React.ReactNode => {
|
||||
const { infusion, ruleData, snippetData, proficiencyBonus } = this.props;
|
||||
|
||||
let contentNode: React.ReactNode;
|
||||
|
||||
if (AccessUtils.isAccessible(InfusionUtils.getAccessType(infusion))) {
|
||||
let classLevel: number | null =
|
||||
InfusionUtils.getSelectedModifierData(infusion)?.value ?? null;
|
||||
|
||||
contentNode = (
|
||||
<Snippet
|
||||
snippetData={snippetData}
|
||||
classLevel={classLevel}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
>
|
||||
{InfusionUtils.getSnippet(infusion)}
|
||||
</Snippet>
|
||||
);
|
||||
} else {
|
||||
const sources = InfusionUtils.getSources(infusion);
|
||||
|
||||
let sourceNames: Array<string> = [];
|
||||
if (sources.length > 0) {
|
||||
sources.forEach((source) => {
|
||||
let sourceInfo = RuleDataUtils.getSourceDataInfo(
|
||||
source.sourceId,
|
||||
ruleData
|
||||
);
|
||||
|
||||
if (sourceInfo !== null && sourceInfo.description !== null) {
|
||||
sourceNames.push(sourceInfo.description);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contentNode = (
|
||||
<MarketplaceCta
|
||||
showImage={false}
|
||||
sourceName={
|
||||
sourceNames.length > 0
|
||||
? FormatUtils.renderNonOxfordCommaList(sourceNames)
|
||||
: null
|
||||
}
|
||||
description="To unlock this infusion, check out the Marketplace to view purchase options."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return contentNode;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { infusion } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ddbc-infusion-preview" onClick={this.handleClick}>
|
||||
<div className="ddbc-infusion-preview__name">
|
||||
Infusion: {InfusionUtils.getName(infusion)}
|
||||
</div>
|
||||
<div className="ddbc-infusion-preview__snippet">
|
||||
{this.renderDescription()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InfusionPreview;
|
||||
@@ -0,0 +1,4 @@
|
||||
import ItemPreview from "./ItemPreview";
|
||||
|
||||
export default ItemPreview;
|
||||
export { ItemPreview };
|
||||
@@ -1,62 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { FormatUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props {
|
||||
sourceName: string | null;
|
||||
sourceNames?: Array<string>;
|
||||
url: string;
|
||||
description: string | null;
|
||||
showImage: boolean;
|
||||
}
|
||||
export default class MarketplaceCta extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
sourceName: null,
|
||||
url: "/marketplace",
|
||||
description: null,
|
||||
showImage: true,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { sourceName, sourceNames, url, description, showImage } = this.props;
|
||||
const allSourceNames: Array<string> = [];
|
||||
|
||||
if (sourceName) {
|
||||
allSourceNames.push(sourceName);
|
||||
}
|
||||
|
||||
if (sourceNames) {
|
||||
allSourceNames.push(...sourceNames);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ddbc-marketplace-cta">
|
||||
<a href={url} target="_blank">
|
||||
<div className="ddbc-marketplace-cta__content">
|
||||
{showImage && <div className="ddbc-marketplace-cta__image" />}
|
||||
{allSourceNames.length > 0 && (
|
||||
<div className="ddbc-marketplace-cta__header">
|
||||
<div className="ddbc-marketplace-cta__header-intro">
|
||||
Available in
|
||||
</div>
|
||||
<div className="ddbc-marketplace-cta__header-title">
|
||||
{FormatUtils.renderNonOxfordCommaList(allSourceNames)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
<div className="ddbc-marketplace-cta__description">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
<div className="ddbc-marketplace-cta__cta">
|
||||
<div className="ddbc-marketplace-cta__cta-button">
|
||||
Go To Marketplace
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import MarketplaceCta from "./MarketplaceCta";
|
||||
|
||||
export default MarketplaceCta;
|
||||
export { MarketplaceCta };
|
||||
@@ -1,128 +0,0 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
SxProps,
|
||||
Theme,
|
||||
} from "@mui/material";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Constants } from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface PrivacyTypeRadioProps {
|
||||
color?: "primary" | "secondary" | "success" | "error" | "warning" | "info";
|
||||
themeColor?: string;
|
||||
compact?: boolean;
|
||||
darkMode?: boolean;
|
||||
handleChange?: (e, value) => void;
|
||||
initialValue?: number | null;
|
||||
sx?: SxProps<Theme>;
|
||||
variant?: "builder" | "sidebar" | "default";
|
||||
themed?: boolean;
|
||||
}
|
||||
|
||||
const privacyOptions = [
|
||||
{
|
||||
value: Constants.PreferencePrivacyTypeEnum.CAMPAIGN_ONLY,
|
||||
label: "Campaign Only",
|
||||
description:
|
||||
"Only others within a Campaign you have joined can view your Characters.",
|
||||
},
|
||||
{
|
||||
value: Constants.PreferencePrivacyTypeEnum.PUBLIC,
|
||||
label: "Public",
|
||||
description: "Anyone with a shared link can view your Characters.",
|
||||
},
|
||||
{
|
||||
value: Constants.PreferencePrivacyTypeEnum.PRIVATE,
|
||||
label: "Private",
|
||||
description: "Only you can view your Characters.",
|
||||
},
|
||||
];
|
||||
|
||||
const PrivacyTypeRadio = ({
|
||||
color,
|
||||
compact,
|
||||
darkMode,
|
||||
initialValue,
|
||||
handleChange,
|
||||
sx,
|
||||
themeColor,
|
||||
variant = "default",
|
||||
themed = false,
|
||||
}: PrivacyTypeRadioProps) => {
|
||||
return (
|
||||
<FormControl
|
||||
sx={sx}
|
||||
className={clsx([
|
||||
styles.container,
|
||||
variant === "sidebar" && styles.containerSidebar,
|
||||
variant === "builder" && styles.containerBuilder,
|
||||
])}
|
||||
>
|
||||
<label
|
||||
id="character-privacy-radio"
|
||||
className={clsx([
|
||||
styles.heading,
|
||||
variant === "sidebar" && styles.headingSidebar,
|
||||
variant === "builder" && styles.headingBuilder,
|
||||
darkMode && styles.headingSidebarDark,
|
||||
])}
|
||||
>
|
||||
Character Privacy
|
||||
</label>
|
||||
<RadioGroup
|
||||
aria-labelledby="character-privacy-radio"
|
||||
name="controlled-radio-buttons-group"
|
||||
value={initialValue}
|
||||
onChange={handleChange}
|
||||
className={clsx([styles.radioGroup])}
|
||||
>
|
||||
{privacyOptions.map((option, i) => (
|
||||
<FormControlLabel
|
||||
key={option.value + i}
|
||||
value={option.value}
|
||||
control={
|
||||
<Radio
|
||||
className={clsx([
|
||||
initialValue === option.value && styles.buttonChecked,
|
||||
initialValue === option.value && themed && styles.themed,
|
||||
variant === "builder" &&
|
||||
initialValue === option.value &&
|
||||
styles.buttonCheckedBuilder,
|
||||
])}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<div className={clsx(styles.radioInfoGroup)}>
|
||||
<label
|
||||
className={clsx([
|
||||
styles.radioHeading,
|
||||
variant === "sidebar" && styles.radioHeadingSidebar,
|
||||
variant === "builder" && styles.radioHeadingBuilder,
|
||||
])}
|
||||
>
|
||||
{option.label}
|
||||
</label>
|
||||
<label
|
||||
className={clsx([
|
||||
styles.radioDescription,
|
||||
variant === "sidebar" && styles.radioDescriptionSidebar,
|
||||
darkMode && styles.radioDescriptionDark,
|
||||
])}
|
||||
>
|
||||
{option.description}
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyTypeRadio;
|
||||
@@ -1,4 +0,0 @@
|
||||
import PrivacyTypeRadio from "./PrivacyTypeRadio";
|
||||
|
||||
export default PrivacyTypeRadio;
|
||||
export { PrivacyTypeRadio };
|
||||
@@ -1,5 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { useContext } from "react";
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import {
|
||||
AbilityManager,
|
||||
@@ -8,9 +6,14 @@ import {
|
||||
SituationalSavingThrowInfo,
|
||||
SituationalSavingThrowInfoLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { DiceTools, IRollContext, RollType } from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import { RollTypes } from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import BoxBackground from "../BoxBackground";
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import { ProficiencyLevelIcon } from "../Icons";
|
||||
import {
|
||||
ThemedSavingThrowSelectionBoxSvg,
|
||||
@@ -22,9 +25,6 @@ import {
|
||||
DarkModePositiveBonusPositiveSvg,
|
||||
DarkModeNegativeBonusNegativeSvg,
|
||||
} from "../Svg";
|
||||
import { isNotNullOrUndefined } from "../utils/TypeScriptUtils";
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
|
||||
interface Props {
|
||||
abilities: Array<AbilityManager>;
|
||||
@@ -34,8 +34,8 @@ interface Props {
|
||||
className?: string;
|
||||
diceEnabled?: boolean;
|
||||
theme: CharacterTheme;
|
||||
rollContext: IRollContext;
|
||||
};
|
||||
rollContext: RollContext;
|
||||
}
|
||||
|
||||
export default function SavingThrowsSummary({
|
||||
abilities,
|
||||
@@ -58,9 +58,6 @@ export default function SavingThrowsSummary({
|
||||
}
|
||||
};
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
useContext(GameLogContext);
|
||||
|
||||
return (
|
||||
<div className="ddbc-saving-throws-summary">
|
||||
{abilities.map((ability) => {
|
||||
@@ -108,6 +105,7 @@ export default function SavingThrowsSummary({
|
||||
StyleComponent = ThemedSavingThrowRowSmallBoxSvg;
|
||||
SelectionBox = ThemedSavingThrowSelectionSmallBoxSvg;
|
||||
}
|
||||
const modifier = ability.getSave();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -124,7 +122,9 @@ export default function SavingThrowsSummary({
|
||||
themeColor: `${theme.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<h3 style={visuallyHidden}>{ability.getLabel()} Saving Throw</h3>
|
||||
<h3 className={a11yStyles.screenreaderOnly}>
|
||||
{ability.getLabel()} Saving Throw
|
||||
</h3>
|
||||
<div className="ddbc-saving-throws-summary__ability-proficiency">
|
||||
<ProficiencyLevelIcon
|
||||
proficiencyLevel={ability.getProficiencyLevel()}
|
||||
@@ -148,21 +148,15 @@ export default function SavingThrowsSummary({
|
||||
</div>
|
||||
<div className="ddbc-saving-throws-summary__ability-modifier">
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceTools.CustomD20(ability.getSave())}
|
||||
rollType={RollType.Save}
|
||||
rollAction={ability.getName()}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions?.entities
|
||||
? Object.values(messageTargetOptions.entities).filter(
|
||||
isNotNullOrUndefined
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={Number(userId)}
|
||||
diceNotation={`1d20${
|
||||
modifier > 0 ? `+${modifier}` : modifier !== 0 ? modifier : ""
|
||||
}`}
|
||||
rollType={RollTypes.Save}
|
||||
action={ability.getName()}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
<NumberDisplay
|
||||
number={ability.getSave()}
|
||||
@@ -192,4 +186,4 @@ export default function SavingThrowsSummary({
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { PureComponent, PropsWithChildren, ReactNode } from "react";
|
||||
import JsxParser from "react-jsx-parser";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
snippetData: SnippetData;
|
||||
levelScale?: LevelScaleContract | null;
|
||||
classLevel?: number | null;
|
||||
@@ -24,7 +24,7 @@ interface Props {
|
||||
theme?: CharacterTheme;
|
||||
proficiencyBonus: number;
|
||||
}
|
||||
export default class Snippet extends React.PureComponent<Props> {
|
||||
export default class Snippet extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
parseSnippet: true,
|
||||
@@ -113,13 +113,13 @@ export default class Snippet extends React.PureComponent<Props> {
|
||||
classNames.push("ddbc-snippet--dark-mode");
|
||||
}
|
||||
|
||||
let contentNode: React.ReactNode;
|
||||
let contentNode: ReactNode;
|
||||
if (typeof children === "string") {
|
||||
if (parseSnippet) {
|
||||
contentNode = (
|
||||
<JsxParser
|
||||
className="ddbc-snippet__content"
|
||||
components={{ Tooltip }}
|
||||
components={{ Tooltip: Tooltip as any }}
|
||||
jsx={this.convertSnippetToHtml(children)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import {
|
||||
ConditionUtils,
|
||||
@@ -14,12 +16,17 @@ import {
|
||||
SpellUtils,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { RollType, RollKind, IRollContext } from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import {
|
||||
RollKinds,
|
||||
RollTypes,
|
||||
} from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import Damage from "../Damage";
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import { HealingIcon } from "../Icons";
|
||||
import { DiceComponentUtils } from "../utils";
|
||||
import { DigitalDiceWrapper } from "../Dice";
|
||||
import Damage from "../Damage";
|
||||
|
||||
interface Props {
|
||||
spell: Spell;
|
||||
@@ -29,7 +36,8 @@ interface Props {
|
||||
diceEnabled: boolean;
|
||||
theme: CharacterTheme;
|
||||
isCriticalHit?: boolean;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
rollCallback: () => void;
|
||||
}
|
||||
class SpellDamageEffect extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
@@ -56,11 +64,9 @@ class SpellDamageEffect extends React.PureComponent<Props> {
|
||||
theme,
|
||||
isCriticalHit,
|
||||
rollContext,
|
||||
rollCallback,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
let isScaled: boolean = false;
|
||||
const modifiers = SpellUtils.getModifiers(spell);
|
||||
const tags = SpellUtils.getTags(spell);
|
||||
@@ -102,19 +108,14 @@ class SpellDamageEffect extends React.PureComponent<Props> {
|
||||
<span className="ddbc-spell-damage-effect__damages">
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceUtils.renderDice(scaledDamageDie)}
|
||||
rollType={RollType.Damage}
|
||||
rollAction={SpellUtils.getName(spell)}
|
||||
rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Damage}
|
||||
action={SpellUtils.getName(spell)}
|
||||
rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
rollCallback={rollCallback}
|
||||
>
|
||||
<Damage
|
||||
damage={DiceComponentUtils.getDamageDiceNotation(
|
||||
@@ -163,19 +164,14 @@ class SpellDamageEffect extends React.PureComponent<Props> {
|
||||
displayNode = (
|
||||
<span className="ddbc-spell-damage-effect__healing ddbc-spell-damage-effect__healing--hp">
|
||||
<DigitalDiceWrapper
|
||||
useAdvancedMenu={false}
|
||||
isContextMenuEnabled={false}
|
||||
diceNotation={DiceUtils.renderDice(scaledHealingDie)}
|
||||
rollType={RollType.Heal}
|
||||
rollAction={SpellUtils.getName(spell)}
|
||||
diceEnabled={diceEnabled}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Heal}
|
||||
action={SpellUtils.getName(spell)}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
{DiceUtils.renderDice(scaledHealingDie)}{" "}
|
||||
<HealingIcon theme={theme} isHp={true} />
|
||||
@@ -217,19 +213,14 @@ class SpellDamageEffect extends React.PureComponent<Props> {
|
||||
displayNode = (
|
||||
<span className="ddbc-spell-damage-effect__healing ddbc-spell-damage-effect__healing--temp">
|
||||
<DigitalDiceWrapper
|
||||
useAdvancedMenu={false}
|
||||
isContextMenuEnabled={false}
|
||||
diceNotation={DiceUtils.renderDice(scaledHealingDie)}
|
||||
rollType={RollType.Heal}
|
||||
rollAction={SpellUtils.getName(spell)}
|
||||
diceEnabled={diceEnabled}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Heal}
|
||||
action={SpellUtils.getName(spell)}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
{DiceUtils.renderDice(scaledHealingDie)}{" "}
|
||||
<HealingIcon theme={theme} isTemp={true} />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
className: string;
|
||||
viewBox: string;
|
||||
preserveAspectRatio?: string;
|
||||
}
|
||||
|
||||
export default class BaseSvg extends React.PureComponent<Props> {
|
||||
export default class BaseSvg extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
viewBox: "0 0 100 100",
|
||||
|
||||
+3
-4
@@ -2,11 +2,10 @@ import React from "react";
|
||||
|
||||
import BaseSvg from "../../BaseSvg";
|
||||
import { InjectedSvgProps } from "../../hocs";
|
||||
import SavingThrowRowBoxSvg from "../SavingThrowRowBoxSvg";
|
||||
|
||||
export default class SavingThrowSelectionBoxSvg extends React.PureComponent<InjectedSvgProps> {
|
||||
render() {
|
||||
const { fillColor, className } = this.props;
|
||||
const { className } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-saving-throw-selection-box-svg"];
|
||||
|
||||
@@ -15,7 +14,7 @@ export default class SavingThrowSelectionBoxSvg extends React.PureComponent<Inje
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<BaseSvg className={classNames.join(" ")} viewBox="0 0 41 36">
|
||||
<path
|
||||
d="M10.8724 1H30.3426C30.9246 1 31.4467 1.15738 31.8408 1.45807C35.0159 3.88086 40 9.69489 40 18C40 26.3051 35.0159 32.1191 31.8408 34.5419C31.4467 34.8426 30.9246 35 30.3426 35H10.8724C10.1907 35 9.57613 34.7818 9.1371 34.3765C6.08124 31.555 1.5 25.4781 1.5 18C1.5 10.5219 6.08124 4.44504 9.1371 1.62354C9.57613 1.21818 10.1907 1 10.8724 1Z"
|
||||
@@ -23,7 +22,7 @@ export default class SavingThrowSelectionBoxSvg extends React.PureComponent<Inje
|
||||
strokeWidth="1"
|
||||
/>
|
||||
</BaseSvg>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
import BaseSvg from "../../../BaseSvg";
|
||||
import { InjectedSvgProps } from "../../../hocs";
|
||||
|
||||
export default class SenseRowBoxSvg extends React.PureComponent<InjectedSvgProps> {
|
||||
render() {
|
||||
const { fillColor, className } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-sense-row-box-svg"];
|
||||
|
||||
if (className) {
|
||||
classNames.push(className);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSvg className={classNames.join(" ")} viewBox="0 0 43 33">
|
||||
<g clip-path="url(#clip0_10400_116)">
|
||||
<path
|
||||
d="M33.3884 32.8019C36.1648 30.7836 38.2245 28.8596 40.0014 26.3792C40.2336 26.002 40.4557 25.6247 40.6678 25.238C40.6678 25.238 40.6678 25.2192 40.6779 25.2192C40.8091 24.9834 40.9202 24.7476 41.0312 24.5118C42.2125 22.0125 42.9798 19.0983 42.9798 15.8917C42.9798 15.3541 42.9495 14.8353 42.9091 14.3261C42.9091 14.2978 42.9091 14.2695 42.9091 14.2506C42.8788 13.9394 42.8284 13.6376 42.7779 13.3358C42.5053 11.723 42.0308 10.1669 41.3543 8.67673C39.4562 5.1966 36.811 2.48982 33.3783 0.207456L33.0653 0.00939941H9.87415L9.56117 0.103712C3.57408 3.80077 0 9.82735 0 16.4481C0 23.0688 3.57408 29.1049 9.55107 32.8019L9.86405 33H33.0653L33.3783 32.8019H33.3884ZM32.3383 2.33892C33.4893 3.12172 34.5393 3.98939 35.4884 4.8665C32.9643 4.37608 30.1374 3.60271 26.7753 2.33892H32.3383ZM10.6011 30.5667C5.66401 27.3506 2.62503 22.0974 2.62503 16.4481C2.62503 10.7988 5.6741 5.54555 10.6011 2.33892H21.4142C26.3513 4.38551 33.0653 5.45124 36.4273 6.03598C38.8403 8.95967 40.3144 12.6567 40.3144 16.4481C40.3144 20.2395 38.9514 23.946 36.4273 26.8602C33.1763 27.445 26.4522 28.5107 21.4142 30.5573H10.6011V30.5667ZM26.7753 30.5667C30.1374 29.3029 33.0754 28.5201 35.4884 28.0391C34.5393 28.9162 33.4893 29.7933 32.3383 30.5667H26.7753Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_10400_116">
|
||||
<rect width="43" height="33" fill={fillColor} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import SenseRowMinimalSvg from "./SenseRowMinimalSvg";
|
||||
|
||||
export default SenseRowMinimalSvg;
|
||||
@@ -1,11 +1,14 @@
|
||||
import { asEmptySvg, asThemedSvg } from "../../hocs";
|
||||
import SenseRowBoxSvg from "./SenseRowBoxSvg";
|
||||
import SenseRowMinimalSvg from "./SenseRowMinimalSvg";
|
||||
import SenseRowSmallBoxSvg from "./SenseRowSmallBoxSvg";
|
||||
|
||||
const EmptySenseRowBoxSvg = asEmptySvg(SenseRowBoxSvg);
|
||||
const ThemedSenseRowBoxSvg = asThemedSvg(SenseRowBoxSvg);
|
||||
const EmptySenseRowSmallBoxSvg = asEmptySvg(SenseRowSmallBoxSvg);
|
||||
const ThemedSenseRowSmallBoxSvg = asThemedSvg(SenseRowSmallBoxSvg);
|
||||
const EmptySenseRowMinimalSvg = asEmptySvg(SenseRowMinimalSvg);
|
||||
const ThemedSenseRowMinimalSvg = asThemedSvg(SenseRowMinimalSvg);
|
||||
|
||||
export default SenseRowBoxSvg;
|
||||
export {
|
||||
@@ -15,4 +18,7 @@ export {
|
||||
ThemedSenseRowBoxSvg,
|
||||
EmptySenseRowSmallBoxSvg,
|
||||
ThemedSenseRowSmallBoxSvg,
|
||||
SenseRowMinimalSvg,
|
||||
EmptySenseRowMinimalSvg,
|
||||
ThemedSenseRowMinimalSvg,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import SilverCoinSvg from "./SilverCoinSvg";
|
||||
|
||||
export default SilverCoinSvg;
|
||||
|
||||
export { SilverCoinSvg };
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantBuilderTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asBuilderSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsBuilderSvg extends React.PureComponent<
|
||||
return class AsBuilderSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asBuilderSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantDarkModeNegativeTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asDarkModeNegativeSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsDarkModeNegativeSvg extends React.PureComponent<
|
||||
return class AsDarkModeNegativeSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asDarkModeNegativeSvg(${getDisplayName(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantDarkModePositiveTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asDarkModePositiveSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsDarkModePositiveSvg extends React.PureComponent<
|
||||
return class AsDarkModePositiveSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asDarkModePositiveSvg(${getDisplayName(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantDarkTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asDarkSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsDarkSvg extends React.PureComponent<
|
||||
return class AsDarkSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asDarkSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantDisabledTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asDisabledSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsDisabledSvg extends React.PureComponent<
|
||||
return class AsDisabledSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asDisabledSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantEmptyTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asEmptySvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsEmptySvg extends React.PureComponent<
|
||||
return class AsEmptySvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asEmptySvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantGrayTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asGraySvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsGraySvg extends React.PureComponent<
|
||||
return class AsGraySvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asGraySvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantLightTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asLightSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsLightSvg extends React.PureComponent<
|
||||
return class AsLightSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asLightSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantModifiedTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asModifiedSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsModifiedSvg extends React.PureComponent<
|
||||
return class AsModifiedSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asModifiedSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantNegativeTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asNegativeSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsNegativeSvg extends React.PureComponent<
|
||||
return class AsNegativeSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asNegativeSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import { SvgConstantPositiveTheme } from "../SvgConstants";
|
||||
import { InjectedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asPositiveSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C> & InjectedSvgProps>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsPositiveSvg extends React.PureComponent<
|
||||
return class AsPositiveSvg extends PureComponent<
|
||||
ResolvedProps & InjectedSvgProps
|
||||
> {
|
||||
static displayName = `asPositiveSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, PureComponent } from "react";
|
||||
|
||||
import { ThemedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asThemedSvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C>>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsThemedSvg extends React.PureComponent<ThemedSvgProps> {
|
||||
export function asThemedSvg<C extends ComponentType<ComponentProps<C>>>(
|
||||
WrappedComponent: C
|
||||
) {
|
||||
return class AsThemedSvg extends PureComponent<ThemedSvgProps> {
|
||||
static displayName = `asThemedSvg(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import Color from "color";
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, PureComponent } from "react";
|
||||
|
||||
import { ThemedSvgProps } from "./hocTypings";
|
||||
import { getDisplayName } from "./utils";
|
||||
|
||||
export function asThemedWithOpacitySvg<
|
||||
C extends React.ComponentType<React.ComponentProps<C>>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C>>
|
||||
>(WrappedComponent: C) {
|
||||
return class AsThemedWithOpacitSvg extends React.PureComponent<ThemedSvgProps> {
|
||||
return class AsThemedWithOpacitSvg extends PureComponent<ThemedSvgProps> {
|
||||
static displayName = `asThemedWithOpacitySvg(${getDisplayName(
|
||||
WrappedComponent
|
||||
)})`;
|
||||
|
||||
+10
-4
@@ -2,15 +2,21 @@ import React from "react";
|
||||
|
||||
import { BaseSvgProps } from "../../BaseSvg";
|
||||
|
||||
export default class AnimatedLoadingRingSvg extends React.PureComponent<BaseSvgProps> {
|
||||
interface AnimatedLoadingRingSvgProps extends BaseSvgProps {
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export default class AnimatedLoadingRingSvg extends React.PureComponent<AnimatedLoadingRingSvgProps> {
|
||||
static defaultProps = {
|
||||
fillColor: "#EC2127",
|
||||
secondaryFillColor: "#131315",
|
||||
backgroundColor: "transparent",
|
||||
className: "",
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fillColor, secondaryFillColor, className } = this.props;
|
||||
const { fillColor, secondaryFillColor, backgroundColor, className } =
|
||||
this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-animated-loading-ring-svg"];
|
||||
|
||||
@@ -18,7 +24,7 @@ export default class AnimatedLoadingRingSvg extends React.PureComponent<BaseSvgP
|
||||
classNames.push(className);
|
||||
}
|
||||
|
||||
const htmlContent: string = `<html><head><style>* {margin:0}</style></head><body style="background-color: transparent;">
|
||||
const htmlContent: string = `<html><head><style>* {margin:0}</style></head><body style="background-color: ${backgroundColor};">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring-alt">
|
||||
<rect x="0" y="0" width="100" height="100" fill="none" class="bk"/>
|
||||
<circle cx="50" cy="50" r="40" stroke="${secondaryFillColor}" fill="none" stroke-width="10" stroke-linecap="round"/>
|
||||
@@ -33,7 +39,7 @@ export default class AnimatedLoadingRingSvg extends React.PureComponent<BaseSvgP
|
||||
className={classNames.join(" ")}
|
||||
frameBorder="0"
|
||||
title="loading"
|
||||
style={{ backgroundColor: "transparent" }}
|
||||
style={{ backgroundColor: backgroundColor }}
|
||||
src={`data:text/html;base64,${btoa(htmlContent)}`}
|
||||
></iframe>
|
||||
);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import BaseSvg from "../../BaseSvg";
|
||||
import { InjectedSvgProps } from "../../hocs";
|
||||
|
||||
export default class ChatBubbleSvg extends React.PureComponent<InjectedSvgProps> {
|
||||
render() {
|
||||
const { fillColor, className } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-chat-bubble-svg"];
|
||||
|
||||
if (className) {
|
||||
classNames.push(className);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSvg className={classNames.join(" ")} viewBox="0 0 21 21">
|
||||
<path
|
||||
fill={fillColor}
|
||||
d="M17.5 11.6666C17.5 12.3095 16.9761 12.8333 16.3333 12.8333H10.5C10.1908 12.8333 9.89329 12.9558 9.67513 13.1751L6.99996 15.8503V14C6.99996 13.3548 6.47729 12.8333 5.83329 12.8333H4.66663C4.02379 12.8333 3.49996 12.3095 3.49996 11.6666V4.66663C3.49996 4.02379 4.02379 3.49996 4.66663 3.49996H16.3333C16.9761 3.49996 17.5 4.02379 17.5 4.66663V11.6666ZM16.3333 1.16663H4.66663C2.73696 1.16663 1.16663 2.73696 1.16663 4.66663V11.6666C1.16663 13.5963 2.73696 15.1666 4.66663 15.1666V18.6666C4.66663 19.138 4.95129 19.565 5.38646 19.7446C5.53113 19.8041 5.68279 19.8333 5.83329 19.8333C6.13663 19.8333 6.43529 19.7143 6.65813 19.4915L10.983 15.1666H16.3333C18.263 15.1666 19.8333 13.5963 19.8333 11.6666V4.66663C19.8333 2.73696 18.263 1.16663 16.3333 1.16663ZM14 6.99996H6.99996C6.35596 6.99996 5.83329 7.52146 5.83329 8.16663C5.83329 8.81179 6.35596 9.33329 6.99996 9.33329H14C14.644 9.33329 15.1666 8.81179 15.1666 8.16663C15.1666 7.52146 14.644 6.99996 14 6.99996Z"
|
||||
/>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { asDarkSvg, asLightSvg, asPositiveSvg, asThemedSvg } from "../../hocs";
|
||||
import ChatBubbleSvg from "./ChatBubbleSvg";
|
||||
|
||||
const LightChatBubbleSvg = asLightSvg(ChatBubbleSvg);
|
||||
const DarkChatBubbleSvg = asDarkSvg(ChatBubbleSvg);
|
||||
const ThemedChatBubbleSvg = asThemedSvg(ChatBubbleSvg);
|
||||
const PositiveChatBubbleSvg = asPositiveSvg(ChatBubbleSvg);
|
||||
|
||||
export default ChatBubbleSvg;
|
||||
export {
|
||||
ChatBubbleSvg,
|
||||
LightChatBubbleSvg,
|
||||
ThemedChatBubbleSvg,
|
||||
DarkChatBubbleSvg,
|
||||
PositiveChatBubbleSvg,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import BaseSvg from "../../BaseSvg";
|
||||
import { InjectedSvgProps } from "../../hocs";
|
||||
|
||||
export default class ExportSvg extends React.PureComponent<InjectedSvgProps> {
|
||||
render() {
|
||||
const { fillColor, className } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-export-svg"];
|
||||
|
||||
if (className) {
|
||||
classNames.push(className);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSvg className={classNames.join(" ")} viewBox="0 0 13 16">
|
||||
<path
|
||||
d="M9 0H0V16H13V4L9 0ZM8.7 1.8L10.7 3.8H8.7V1.8ZM1.5 14.5V12.4H9.1V10.9H1.5V8.7H9.1V7.2H1.5V5H4.3V3.5H1.5V1.5H7.2V5.3H11.5V14.5H1.5Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { asDarkSvg, asLightSvg, asThemedSvg, asDisabledSvg } from "../../hocs";
|
||||
import ExportSvg from "./ExportSvg";
|
||||
|
||||
const LightExportSvg = asLightSvg(ExportSvg);
|
||||
const DarkExportSvg = asDarkSvg(ExportSvg);
|
||||
const ThemedExportSvg = asThemedSvg(ExportSvg);
|
||||
const DisabledExportSvg = asDisabledSvg(ExportSvg);
|
||||
|
||||
export default ExportSvg;
|
||||
export {
|
||||
ExportSvg,
|
||||
DarkExportSvg,
|
||||
LightExportSvg,
|
||||
ThemedExportSvg,
|
||||
DisabledExportSvg,
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import BaseSvg from "../../BaseSvg";
|
||||
import { InjectedSvgProps } from "../../hocs";
|
||||
|
||||
export default class LinkOutSvg extends React.PureComponent<InjectedSvgProps> {
|
||||
render() {
|
||||
const { fillColor, className } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ddbc-link-out-svg"];
|
||||
|
||||
if (className) {
|
||||
classNames.push(className);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSvg className={classNames.join(" ")} viewBox="0 0 18 18">
|
||||
<path
|
||||
d="M16.7068 1.293C16.8028 1.389 16.8758 1.5 16.9248 1.619C16.9728 1.735 16.9998 1.863 16.9998 1.997V2V7C16.9998 7.553 16.5518 8 15.9998
|
||||
8C15.4478 8 14.9998 7.553 14.9998 7V4.414L8.7068 10.707C8.5118 10.902 8.2558 11 7.9998 11C7.7438 11 7.4878 10.902 7.2928 10.707C6.9028 10.316
|
||||
6.9028 9.683 7.2928 9.293L13.5858 3H10.9998C10.4478 3 9.9998 2.553 9.9998 2C9.9998 1.447 10.4478 1 10.9998 1H15.9998H16.0028C16.1368 1 16.2648
|
||||
1.027 16.3808 1.075C16.4998 1.124 16.6108 1.197 16.7068 1.293ZM12 10.9999C12 10.4469 12.448 9.9999 13 9.9999C13.552 9.9999 14 10.4469 14
|
||||
10.9999V15.9999C14 16.5529 13.552 16.9999 13 16.9999H2C1.448 16.9999 1 16.5529 1 15.9999V4.9999C1 4.4469 1.448 3.9999 2 3.9999H7C7.552
|
||||
3.9999 8 4.4469 8 4.9999C8 5.5529 7.552 5.9999 7 5.9999H3V14.9999H12V10.9999Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { asLightSvg, asThemedSvg } from "../../hocs";
|
||||
import LinkOutSvg from "./LinkOutSvg";
|
||||
|
||||
const LightLinkOutSvg = asLightSvg(LinkOutSvg);
|
||||
const ThemedLinkOutSvg = asThemedSvg(LinkOutSvg);
|
||||
|
||||
export default LinkOutSvg;
|
||||
export { LinkOutSvg, LightLinkOutSvg, ThemedLinkOutSvg };
|
||||
@@ -1,4 +0,0 @@
|
||||
import ThemeStyles from "./ThemeStyles";
|
||||
|
||||
export default ThemeStyles;
|
||||
export { ThemeStyles };
|
||||
@@ -1,75 +0,0 @@
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
|
||||
import { RuleData } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import {
|
||||
getUserPreferences,
|
||||
updateUserPreferences,
|
||||
getRulesData,
|
||||
} from "~/helpers/characterServiceApi";
|
||||
import { UserPreferences } from "~/types";
|
||||
|
||||
import PreferenceUpdateLocation from "../../Shared/constants/PreferenceUpdateLocation";
|
||||
|
||||
interface UserPreferenceContextProps extends UserPreferences {
|
||||
updatePreferences: (updated: UserPreferences) => void;
|
||||
ruleData: RuleData | null;
|
||||
}
|
||||
|
||||
const initialValue = {
|
||||
abilityScoreDisplayType: 2,
|
||||
privacyType: 2,
|
||||
isDarkModeEnabled: false,
|
||||
isDiceRollingEnabled: true,
|
||||
updateLocation: PreferenceUpdateLocation.CharacterBuilder,
|
||||
updatePreferences: () => {},
|
||||
ruleData: null,
|
||||
defaultEnabledSourceCategories: {},
|
||||
isHomebrewEnabled: true,
|
||||
};
|
||||
|
||||
export const UserPreferenceContext =
|
||||
createContext<UserPreferenceContextProps>(initialValue);
|
||||
|
||||
export const UserPreferenceProvider = ({ children }) => {
|
||||
const [preferences, setPreferences] = useState(initialValue);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [ruleData, setRuleData] = useState(null);
|
||||
|
||||
const updatePreferences = (updated: UserPreferences) => {
|
||||
setPreferences((prev) => {
|
||||
return { ...prev, ...updated };
|
||||
});
|
||||
};
|
||||
|
||||
const getPreferences = async () => {
|
||||
const userPreferences = await getUserPreferences();
|
||||
const userPreferencesJSON = await userPreferences.json();
|
||||
|
||||
const rulesData = await getRulesData();
|
||||
const rulesDataJSON = await rulesData.json();
|
||||
|
||||
setRuleData(rulesDataJSON.data);
|
||||
updatePreferences(userPreferencesJSON.data);
|
||||
setIsLoaded(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getPreferences();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoaded) {
|
||||
updateUserPreferences(preferences);
|
||||
}
|
||||
}, [preferences]);
|
||||
|
||||
return (
|
||||
<UserPreferenceContext.Provider
|
||||
value={{ ...preferences, updatePreferences, ruleData }}
|
||||
>
|
||||
{children}
|
||||
</UserPreferenceContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, {
|
||||
DependencyList,
|
||||
EffectCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
/**
|
||||
* React.useEffect, except that it never runs on mount.
|
||||
* This is emulating the componentDidUpdate lifecycle function.
|
||||
*/
|
||||
export const useEffectExceptOnMount = (
|
||||
effect: EffectCallback,
|
||||
dependencies?: DependencyList
|
||||
): void => {
|
||||
const mounted = useRef(false);
|
||||
useEffect(() => {
|
||||
if (mounted.current) {
|
||||
const unmount = effect();
|
||||
return () => unmount && unmount();
|
||||
} else {
|
||||
mounted.current = true;
|
||||
}
|
||||
}, dependencies);
|
||||
|
||||
//Reset on unmount for the next mount.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { MouseEvent, PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export interface ButtonProps {
|
||||
export interface ButtonProps extends PropsWithChildren {
|
||||
onClick?: ((evt: React.MouseEvent) => void) | (() => void);
|
||||
clsNames?: Array<string>;
|
||||
className: string;
|
||||
@@ -24,7 +24,7 @@ interface ButtonState {
|
||||
currentDuration: number;
|
||||
confirmText: string;
|
||||
}
|
||||
export class Button extends React.PureComponent<ButtonProps, ButtonState> {
|
||||
export class Button extends PureComponent<ButtonProps, ButtonState> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
styledDisabled: false,
|
||||
@@ -56,7 +56,7 @@ export class Button extends React.PureComponent<ButtonProps, ButtonState> {
|
||||
clearInterval(this.confirmIntervalId);
|
||||
}
|
||||
|
||||
handleConfirmClick = (evt: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
handleConfirmClick = (evt: MouseEvent<HTMLButtonElement>): void => {
|
||||
const { isConfirming, isConfirmed } = this.state;
|
||||
const {
|
||||
confirmText,
|
||||
@@ -120,7 +120,7 @@ export class Button extends React.PureComponent<ButtonProps, ButtonState> {
|
||||
}
|
||||
};
|
||||
|
||||
handleClick = (evt: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
handleClick = (evt: MouseEvent<HTMLButtonElement>): void => {
|
||||
const { onClick, stopPropagation, isInteractive } = this.props;
|
||||
|
||||
if (onClick && isInteractive) {
|
||||
@@ -229,10 +229,10 @@ export class Button extends React.PureComponent<ButtonProps, ButtonState> {
|
||||
>
|
||||
<span className="ct-button__content">{children}</span>
|
||||
{enableConfirm && (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<span className="ct-button__confirming">{confirmText}</span>
|
||||
<span className="ct-button__confirmed" />
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { FC } from "react";
|
||||
|
||||
import Button, { ButtonProps } from "../Button";
|
||||
|
||||
//Duplicated from CharacterToolsClient
|
||||
// Duplicated from CharacterToolsClient
|
||||
// ToolsClient implements as a ThemeButton wrapper
|
||||
// eventually RemoveButton usage should be replaced with this one with specialized style overrides
|
||||
|
||||
export interface RemoveButtonProps extends Partial<ButtonProps> {}
|
||||
const RemoveButton: React.FunctionComponent<RemoveButtonProps> = ({
|
||||
const RemoveButton: FC<RemoveButtonProps> = ({
|
||||
size = "small",
|
||||
style = "outline",
|
||||
stopPropagation = true,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//Button and sub-buttons should be moved to common lib
|
||||
import Button from "./Button";
|
||||
import RemoveButton from "./RemoveButton";
|
||||
|
||||
export default Button;
|
||||
export { Button, RemoveButton };
|
||||
@@ -1,244 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
ApiAdapterPromise,
|
||||
ApiAdapterUtils,
|
||||
ApiResponse,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props<T extends any = any, TData extends any = any> {
|
||||
loadOptions?: () => ApiAdapterPromise<ApiResponse<Array<TData>>>;
|
||||
clsNames: Array<string>;
|
||||
id?: any; //TODO type?
|
||||
options: Array<any>; //TODO type Options
|
||||
disabled: boolean;
|
||||
value: React.ReactText | null;
|
||||
placeholder: string;
|
||||
onChange?: (value: string) => void;
|
||||
onChangePromise?: (
|
||||
newValue: string,
|
||||
oldValue: string,
|
||||
accept: () => void,
|
||||
reject: () => void
|
||||
) => void;
|
||||
parseLoadedOptions?: (data: Array<TData>) => Array<T>;
|
||||
resetAfterChoice: boolean;
|
||||
initialOptionRemoved: boolean;
|
||||
preventClickPropagating: boolean;
|
||||
isReadonly?: boolean;
|
||||
className: string;
|
||||
}
|
||||
interface State {
|
||||
isLazy: boolean;
|
||||
lazyOptions: Array<any>;
|
||||
value: React.ReactText;
|
||||
resetAfterChoiceValues: Array<string>;
|
||||
}
|
||||
export default class Select extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
clsNames: [],
|
||||
className: "",
|
||||
disabled: false,
|
||||
resetAfterChoice: false,
|
||||
initialOptionRemoved: false,
|
||||
placeholder: "-- Choose an Option --",
|
||||
options: [],
|
||||
preventClickPropagating: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLazy: !!props.loadOptions,
|
||||
lazyOptions: [],
|
||||
value: props.value === null ? "" : props.value,
|
||||
resetAfterChoiceValues: this.getResetAfterValueChoices(props),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<Props>,
|
||||
prevState: Readonly<State>,
|
||||
snapshot?: any
|
||||
): void {
|
||||
const { value } = this.props;
|
||||
|
||||
if (value !== prevProps.value) {
|
||||
this.setState({
|
||||
value: value === null ? "" : value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { isLazy } = this.state;
|
||||
const { loadOptions, parseLoadedOptions } = this.props;
|
||||
|
||||
if (isLazy && loadOptions && parseLoadedOptions) {
|
||||
loadOptions().then((response) => {
|
||||
const data = ApiAdapterUtils.getResponseData(response);
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
lazyOptions: parseLoadedOptions(data),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getResetAfterValueChoices = (props: Props): Array<string> => {
|
||||
const { options, resetAfterChoice } = props;
|
||||
|
||||
if (resetAfterChoice) {
|
||||
return options.reduce((acc, option) => {
|
||||
if (option.options) {
|
||||
acc.push(...option.options.map((opt) => "" + opt.value));
|
||||
} else {
|
||||
acc.push("" + option.value);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return options.reduce((acc, option) => {
|
||||
if (option.options && option.resetAfterChoice) {
|
||||
acc.push(...option.options.map((opt) => "" + opt.value));
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
handleChange = (evt: React.ChangeEvent<HTMLSelectElement>): void => {
|
||||
const { value, resetAfterChoiceValues } = this.state;
|
||||
const { onChangePromise, onChange, resetAfterChoice } = this.props;
|
||||
|
||||
const newValue: string = evt.target.value;
|
||||
|
||||
if (onChangePromise) {
|
||||
onChangePromise(
|
||||
newValue,
|
||||
String(value),
|
||||
() => {
|
||||
this.setState({
|
||||
value: newValue,
|
||||
});
|
||||
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
|
||||
if (resetAfterChoice) {
|
||||
evt.target.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
} else {
|
||||
if (resetAfterChoice || resetAfterChoiceValues.includes(newValue)) {
|
||||
evt.target.selectedIndex = 0;
|
||||
} else {
|
||||
this.setState({
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
const { preventClickPropagating } = this.props;
|
||||
|
||||
if (preventClickPropagating) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
|
||||
renderOption = (option: any): React.ReactNode => {
|
||||
return (
|
||||
<option
|
||||
key={option.value}
|
||||
value={String(option.value)}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{option.label}
|
||||
</option>
|
||||
);
|
||||
};
|
||||
|
||||
renderOptGroup = (
|
||||
label: string,
|
||||
options: Array<any>,
|
||||
resetAfterChoice: boolean
|
||||
) => {
|
||||
if (!options.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<optgroup label={label} key={label}>
|
||||
{options.map((option) => this.renderOption(option))}
|
||||
</optgroup>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isLazy, lazyOptions, value } = this.state;
|
||||
const {
|
||||
options,
|
||||
placeholder,
|
||||
initialOptionRemoved,
|
||||
id,
|
||||
disabled,
|
||||
clsNames,
|
||||
isReadonly,
|
||||
className,
|
||||
} = this.props;
|
||||
|
||||
let displayOptions: Array<any> = options;
|
||||
if (isLazy) {
|
||||
displayOptions = lazyOptions;
|
||||
}
|
||||
|
||||
const conClassNames: Array<string> = [
|
||||
"ddbc-select",
|
||||
...clsNames,
|
||||
className,
|
||||
];
|
||||
|
||||
return (
|
||||
<select
|
||||
id={id}
|
||||
className={conClassNames.join(" ")}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onClick={this.handleClick}
|
||||
disabled={disabled || isReadonly}
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled={initialOptionRemoved}
|
||||
hidden={initialOptionRemoved}
|
||||
>
|
||||
{placeholder}
|
||||
</option>
|
||||
{displayOptions.map((option) => {
|
||||
if (option.options) {
|
||||
return this.renderOptGroup(
|
||||
option.optGroupLabel,
|
||||
option.options,
|
||||
option.resetAfterChoice
|
||||
);
|
||||
} else {
|
||||
return this.renderOption(option);
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import Select from "./Select";
|
||||
|
||||
export default Select;
|
||||
export { Select };
|
||||
@@ -1,7 +1,11 @@
|
||||
import { orderBy } from "lodash";
|
||||
|
||||
import { Constants } from "@dndbeyond/character-rules-engine/es";
|
||||
import { DiceOperation, DieTerm, RollRequest } from "@dndbeyond/dice";
|
||||
import { DiceOperations } from "@dndbeyond/pocket-dimension-dice/enums";
|
||||
import {
|
||||
RollDicePayload,
|
||||
DiceNotationSet,
|
||||
} from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
interface Hack__OrderableDieValueInfo {
|
||||
idx: number;
|
||||
@@ -15,14 +19,14 @@ interface Hack__OrderableDieValueInfo {
|
||||
* @param rollRequestResult
|
||||
*/
|
||||
export function hack__generateDiceRollResultTermLookup(
|
||||
rollRequestResult: RollRequest
|
||||
): Record<number, DieTerm> {
|
||||
const hack__diceRollResultTermLookup: Record<number, DieTerm> = {};
|
||||
rollRequestResult: RollDicePayload["data"]
|
||||
): Record<number, DiceNotationSet> {
|
||||
const hack__diceRollResultTermLookup: Record<number, DiceNotationSet> = {};
|
||||
|
||||
let dieCurrentIdx: number = 0;
|
||||
rollRequestResult.rolls[0].diceNotation.set.forEach((dieTerm) => {
|
||||
dieTerm.dice.forEach((die) => {
|
||||
hack__diceRollResultTermLookup[dieCurrentIdx] = dieTerm;
|
||||
rollRequestResult.rolls[0].diceNotation.set.forEach((diceNotationSet) => {
|
||||
diceNotationSet?.dice.forEach(() => {
|
||||
hack__diceRollResultTermLookup[dieCurrentIdx] = diceNotationSet;
|
||||
dieCurrentIdx++;
|
||||
});
|
||||
});
|
||||
@@ -37,7 +41,7 @@ export function hack__generateDiceRollResultTermLookup(
|
||||
* @param rollRequestResult
|
||||
*/
|
||||
export function hack__generateDiceRollResultValueExcludeTypeLookup(
|
||||
rollRequestResult: RollRequest
|
||||
rollRequestResult: RollDicePayload["data"]
|
||||
): Record<number, Constants.DiceRollExcludeTypeEnum> {
|
||||
const hack__diceRollResultValueExcludeTypeLookup: Record<
|
||||
number,
|
||||
@@ -50,48 +54,52 @@ export function hack__generateDiceRollResultValueExcludeTypeLookup(
|
||||
|
||||
let dieGroupStartIdx: number = 0;
|
||||
|
||||
rollRequestResult.rolls[0].diceNotation.set.forEach((dieTerm) => {
|
||||
let diceCount = dieTerm.count;
|
||||
rollRequestResult.rolls[0].diceNotation.set.forEach((diceNotationSet) => {
|
||||
let diceCount = diceNotationSet?.count;
|
||||
|
||||
//determine sortOrder based on dieTerm.operation
|
||||
let sortOrder: "asc" | "desc" | null = null;
|
||||
switch (dieTerm.operation) {
|
||||
case DiceOperation.Min:
|
||||
switch (diceNotationSet?.operation) {
|
||||
case DiceOperations.Min:
|
||||
sortOrder = "asc";
|
||||
break;
|
||||
case DiceOperation.Max:
|
||||
case DiceOperations.Max:
|
||||
default:
|
||||
sortOrder = "desc";
|
||||
}
|
||||
|
||||
let dieTermResults =
|
||||
let results =
|
||||
rollResult?.values.slice(
|
||||
dieGroupStartIdx,
|
||||
dieGroupStartIdx + diceCount
|
||||
dieGroupStartIdx + (diceCount ?? 0)
|
||||
) ?? [];
|
||||
let hack__orderableDieValueInfos: Array<Hack__OrderableDieValueInfo> =
|
||||
dieTermResults.map((value, idx) => ({
|
||||
results.map((value, idx) => ({
|
||||
idx: dieGroupStartIdx + idx,
|
||||
value,
|
||||
}));
|
||||
let orderedDieTermResults = orderBy(
|
||||
let orderedResults = orderBy(
|
||||
hack__orderableDieValueInfos,
|
||||
(valueInfo) => valueInfo.value,
|
||||
sortOrder
|
||||
);
|
||||
|
||||
//build-up hack__diceRollResultValueExcludeTypeLookup
|
||||
orderedDieTermResults.slice(0, dieTerm.operand).forEach((dieTermResult) => {
|
||||
hack__diceRollResultValueExcludeTypeLookup[dieTermResult.idx] =
|
||||
Constants.DiceRollExcludeTypeEnum.ALL;
|
||||
});
|
||||
orderedDieTermResults.slice(dieTerm.operand).forEach((dieTermResult) => {
|
||||
hack__diceRollResultValueExcludeTypeLookup[dieTermResult.idx] =
|
||||
Constants.DiceRollExcludeTypeEnum.NONE;
|
||||
});
|
||||
orderedResults
|
||||
.slice(0, diceNotationSet?.operand)
|
||||
.forEach((diceNotationSet) => {
|
||||
hack__diceRollResultValueExcludeTypeLookup[diceNotationSet.idx] =
|
||||
Constants.DiceRollExcludeTypeEnum.ALL;
|
||||
});
|
||||
orderedResults
|
||||
.slice(diceNotationSet?.operand)
|
||||
.forEach((diceNotationSet) => {
|
||||
hack__diceRollResultValueExcludeTypeLookup[diceNotationSet.idx] =
|
||||
Constants.DiceRollExcludeTypeEnum.NONE;
|
||||
});
|
||||
|
||||
//move diceGroupStartIdx
|
||||
dieGroupStartIdx += diceCount;
|
||||
dieGroupStartIdx += diceCount ?? 0;
|
||||
});
|
||||
|
||||
return hack__diceRollResultValueExcludeTypeLookup;
|
||||
|
||||
@@ -5,13 +5,12 @@ import {
|
||||
HelperUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import {
|
||||
DiceEvent,
|
||||
Dice,
|
||||
RollRequest,
|
||||
RollType,
|
||||
RollKind,
|
||||
DiceNotation,
|
||||
} from "@dndbeyond/dice";
|
||||
RollKinds,
|
||||
RollTypes,
|
||||
} from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import { parseDiceNotation } from "@dndbeyond/pocket-dimension-dice/helpers/parseDiceNotation";
|
||||
import { toDiceNotationString } from "@dndbeyond/pocket-dimension-dice/helpers/toDiceNotationString";
|
||||
import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { DiceComponentUtils, TypeScriptUtils } from "../index";
|
||||
import {
|
||||
@@ -19,45 +18,12 @@ import {
|
||||
hack__generateDiceRollResultValueExcludeTypeLookup,
|
||||
} from "./hacks";
|
||||
|
||||
/**
|
||||
* Adds an event listener that sets the state variable isCriticalHit to false when a roll is performed
|
||||
* that is not this component's to-hit roll
|
||||
*
|
||||
* Note: Move this to a hook if the components go classless
|
||||
*/
|
||||
export function setupResetCritStateOnRoll(
|
||||
rollAction: string,
|
||||
rollNode: React.ReactNode
|
||||
): (eventData: any) => void {
|
||||
let handler = _handler.bind(undefined, rollAction, rollNode);
|
||||
Dice.addEventListener(DiceEvent.ROLL, handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
export function _handler(
|
||||
rollAction: string,
|
||||
rollNode: React.PureComponent<any, any>,
|
||||
result: RollRequest
|
||||
): void {
|
||||
let shouldResetCritState =
|
||||
// A multi-roll was performed
|
||||
result.rolls.length !== 1 ||
|
||||
// A roll from another action was performed
|
||||
rollAction != result.action ||
|
||||
// This didn't have a to-hit roll, and we already know it is this same action
|
||||
result.rolls.find((r) => r.rollType != RollType.ToHit);
|
||||
|
||||
if (shouldResetCritState) {
|
||||
rollNode?.setState({ isCriticalHit: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a critical hit
|
||||
*/
|
||||
export function isCriticalRoll(result: RollRequest): boolean {
|
||||
export function isCriticalRoll(result: RollDicePayload["data"]): boolean {
|
||||
// Does this roll have a to-hit roll?
|
||||
const hitRoll = result.rolls.find((r) => r.rollType == RollType.ToHit);
|
||||
const hitRoll = result.rolls.find((r) => r.rollType == RollTypes.ToHit);
|
||||
if (hitRoll) {
|
||||
const minRollForCrit: number = 20;
|
||||
let numCritRolls: number =
|
||||
@@ -65,10 +31,10 @@ export function isCriticalRoll(result: RollRequest): boolean {
|
||||
|
||||
let critRoll: boolean = false;
|
||||
switch (hitRoll.rollKind) {
|
||||
case RollKind.Disadvantage:
|
||||
case RollKinds.Disadvantage:
|
||||
critRoll = numCritRolls >= 2;
|
||||
break;
|
||||
case RollKind.Advantage:
|
||||
case RollKinds.Advantage:
|
||||
default:
|
||||
critRoll = numCritRolls > 0;
|
||||
break;
|
||||
@@ -97,12 +63,16 @@ export function getDamageDiceNotation(
|
||||
return DiceUtils.renderDice(damage);
|
||||
}
|
||||
|
||||
let dn = DiceNotation.parseDiceNotation(DiceUtils.renderDice(damage));
|
||||
let dn = parseDiceNotation(DiceUtils.renderDice(damage));
|
||||
|
||||
for (let i = 0; i < dn.set.length; i++) {
|
||||
// Double the dice on each term
|
||||
dn.set[i].count *= 2;
|
||||
if (dn.set[i]) {
|
||||
dn.set[i]!.count *= 2;
|
||||
}
|
||||
}
|
||||
return dn.toDiceNotationString();
|
||||
|
||||
return toDiceNotationString(dn.set, dn.constant);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +89,7 @@ export function getDieTypeValue(dieType: string): number {
|
||||
* @param rollRequestResult
|
||||
*/
|
||||
export function generateRollValueContracts(
|
||||
rollRequestResult: RollRequest
|
||||
rollRequestResult: RollDicePayload["data"]
|
||||
): Array<RollValueContract> | null {
|
||||
//these hacks make it easier to grab the data from the dice api until the api is updated
|
||||
const hack__diceRollResultTermLookup =
|
||||
|
||||
Reference in New Issue
Block a user