New source found from dndbeyond.com
This commit is contained in:
@@ -112,14 +112,14 @@ export default class ActionSnippet extends React.PureComponent<Props> {
|
||||
if (item !== null) {
|
||||
const infusion = ItemUtils.getInfusion(item);
|
||||
name = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<ItemName item={item} />
|
||||
{infusion !== null && (
|
||||
<span className="ct-feature-snippet__heading-extra">
|
||||
(Infusion: {InfusionUtils.getName(infusion)})
|
||||
</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { orderBy } from "lodash";
|
||||
import React, { HTMLAttributes, useMemo } from "react";
|
||||
|
||||
import { AttackTable } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
AbilityLookup,
|
||||
@@ -21,15 +23,15 @@ import {
|
||||
CharacterTheme,
|
||||
Action,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
|
||||
import ActionSnippet from "../ActionSnippet";
|
||||
import BasicActions from "../BasicActions";
|
||||
import { FeatureSnippetSpells } from "../FeatureSnippet";
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
import { ActionListSection } from "./ActionListSection";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface ActionsListProps extends HTMLAttributes<HTMLDivElement> {
|
||||
heading: React.ReactNode;
|
||||
@@ -49,10 +51,10 @@ interface ActionsListProps extends HTMLAttributes<HTMLDivElement> {
|
||||
abilityLookup: AbilityLookup;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
inventoryLookup: InventoryLookup;
|
||||
snippetData: SnippetData;
|
||||
|
||||
|
||||
// ActionHandlers
|
||||
onAttackClick: (attack: Attack) => void;
|
||||
onActionClick: (action: Action) => void;
|
||||
@@ -60,7 +62,7 @@ interface ActionsListProps extends HTMLAttributes<HTMLDivElement> {
|
||||
onActionUseSet: (action: Action, used: number) => void;
|
||||
onSpellClick: (spell: Spell) => void;
|
||||
onSpellUseSet: (spell: Spell, used: number) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const ActionsList = ({
|
||||
heading,
|
||||
@@ -94,16 +96,17 @@ export const ActionsList = ({
|
||||
className,
|
||||
...props
|
||||
}: ActionsListProps) => {
|
||||
const { actionLookup } = useCharacterEngine();
|
||||
const { actionLookup, itemPlans } = useCharacterEngine();
|
||||
|
||||
const isVisible = useMemo(
|
||||
() => !!basicActions.length
|
||||
|| !!actions.length
|
||||
|| !!ritualSpells.length
|
||||
|| !!attacks.length,
|
||||
() =>
|
||||
!!basicActions.length ||
|
||||
!!actions.length ||
|
||||
!!ritualSpells.length ||
|
||||
!!attacks.length,
|
||||
[basicActions, actions, ritualSpells, attacks]
|
||||
);
|
||||
|
||||
|
||||
const attackTableProps = {
|
||||
attacks,
|
||||
weaponSpellDamageGroups,
|
||||
@@ -117,6 +120,7 @@ export const ActionsList = ({
|
||||
dataOriginRefData,
|
||||
proficiencyBonus,
|
||||
rollContext,
|
||||
itemPlans,
|
||||
};
|
||||
|
||||
const spellSnippetProps = {
|
||||
@@ -146,43 +150,42 @@ export const ActionsList = ({
|
||||
}, [actions]);
|
||||
|
||||
const orderedRitualSpells = useMemo(
|
||||
() => orderBy(
|
||||
ritualSpells,
|
||||
(spell) => SpellUtils.getName(spell)
|
||||
),
|
||||
() => orderBy(ritualSpells, (spell) => SpellUtils.getName(spell)),
|
||||
[ritualSpells]
|
||||
);
|
||||
|
||||
const features = useMemo(() => actions.filter(
|
||||
a => a.type === Constants.ActivatableTypeEnum.ACTION)
|
||||
.map(feature => {
|
||||
const action = HelperUtils.lookupDataOrFallback(
|
||||
actionLookup,
|
||||
ActionUtils.getUniqueKey(feature.entity)
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={styles.activatable}
|
||||
key={feature.key}
|
||||
data-testid="actions-list-activatable-feature"
|
||||
>
|
||||
<ActionSnippet
|
||||
theme={theme}
|
||||
action={action ?? feature.entity}
|
||||
onActionClick={onActionClick}
|
||||
onActionUseSet={onActionUseSet}
|
||||
abilityLookup={abilityLookup}
|
||||
inventoryLookup={inventoryLookup}
|
||||
ruleData={ruleData}
|
||||
snippetData={snippetData}
|
||||
isInteractive={isInteractive}
|
||||
showActivationInfo={showActivationInfo}
|
||||
activation={feature.activation}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
const features = useMemo(
|
||||
() =>
|
||||
actions
|
||||
.filter((a) => a.type === Constants.ActivatableTypeEnum.ACTION)
|
||||
.map((feature) => {
|
||||
const action = HelperUtils.lookupDataOrFallback(
|
||||
actionLookup,
|
||||
ActionUtils.getUniqueKey(feature.entity)
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={styles.activatable}
|
||||
key={feature.key}
|
||||
data-testid="actions-list-activatable-feature"
|
||||
>
|
||||
<ActionSnippet
|
||||
theme={theme}
|
||||
action={action ?? feature.entity}
|
||||
onActionClick={onActionClick}
|
||||
onActionUseSet={onActionUseSet}
|
||||
abilityLookup={abilityLookup}
|
||||
inventoryLookup={inventoryLookup}
|
||||
ruleData={ruleData}
|
||||
snippetData={snippetData}
|
||||
isInteractive={isInteractive}
|
||||
showActivationInfo={showActivationInfo}
|
||||
activation={feature.activation}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
[
|
||||
actions,
|
||||
actionLookup,
|
||||
@@ -196,7 +199,8 @@ export const ActionsList = ({
|
||||
isInteractive,
|
||||
showActivationInfo,
|
||||
proficiencyBonus,
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
return isVisible ? (
|
||||
<div className={clsx([styles.actionsList, className])} {...props}>
|
||||
@@ -207,10 +211,16 @@ export const ActionsList = ({
|
||||
{heading}
|
||||
</div>
|
||||
<div>
|
||||
{!!attacks.length && <AttackTable className={styles.attackTable} {...attackTableProps} />}
|
||||
{!!attacks.length && (
|
||||
<AttackTable className={styles.attackTable} {...attackTableProps} />
|
||||
)}
|
||||
|
||||
{!!basicActions.length && (
|
||||
<ActionListSection headingText="Actions in Combat" testId="basic" theme={theme}>
|
||||
<ActionListSection
|
||||
headingText="Actions in Combat"
|
||||
testId="basic"
|
||||
theme={theme}
|
||||
>
|
||||
<BasicActions
|
||||
onActionClick={onBasicActionClick}
|
||||
basicActions={basicActions}
|
||||
@@ -221,13 +231,21 @@ export const ActionsList = ({
|
||||
|
||||
{!!spells.length && (
|
||||
<ActionListSection headingText="Spells" theme={theme}>
|
||||
<FeatureSnippetSpells layoutType={"compact"} spells={spells} {...spellSnippetProps} />
|
||||
<FeatureSnippetSpells
|
||||
layoutType={"compact"}
|
||||
spells={spells}
|
||||
{...spellSnippetProps}
|
||||
/>
|
||||
</ActionListSection>
|
||||
)}
|
||||
|
||||
{!!ritualSpells.length && (
|
||||
<ActionListSection headingText="Ritual Spells" theme={theme}>
|
||||
<FeatureSnippetSpells layoutType={"compact"} spells={orderedRitualSpells} {...spellSnippetProps} />
|
||||
<FeatureSnippetSpells
|
||||
layoutType={"compact"}
|
||||
spells={orderedRitualSpells}
|
||||
{...spellSnippetProps}
|
||||
/>
|
||||
</ActionListSection>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
FeatureManager,
|
||||
FeaturesManager,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { PaneIdentifierUtils } from "~/tools/js/Shared/utils";
|
||||
import { Snippet } from "~/tools/js/smartComponents";
|
||||
|
||||
import { CharacterFeaturesManagerContext } from "../../../Shared/managers/CharacterFeaturesManagerContext";
|
||||
|
||||
interface Props {
|
||||
// onActionUseSet: (action: Action, uses: number) => void;
|
||||
// onActionClick: (action: Action) => void;
|
||||
// onSpellClick: (spell: Spell) => void;
|
||||
// onSpellUseSet: (spell: Spell, uses: number) => void;
|
||||
// onFeatureClick: (feat: FeatManager) => void;
|
||||
// snippetData: SnippetData;
|
||||
// ruleData: RuleData;
|
||||
// abilityLookup: AbilityLookup;
|
||||
// dataOriginRefData: DataOriginRefData;
|
||||
// isReadonly: boolean;
|
||||
// proficiencyBonus: number;
|
||||
// theme: CharacterTheme;
|
||||
}
|
||||
export const BlessingsDetail: React.FC<Props> = () => {
|
||||
const { characterFeaturesManager } = useContext(
|
||||
CharacterFeaturesManagerContext
|
||||
);
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
} = useSidebar();
|
||||
|
||||
const [blessings, setBlessings] = useState<Array<FeatureManager>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function onUpdate() {
|
||||
const blessings = await characterFeaturesManager.getBlessings();
|
||||
setBlessings(blessings);
|
||||
}
|
||||
return FeaturesManager.subscribeToUpdates({ onUpdate });
|
||||
}, [setBlessings]);
|
||||
|
||||
const snippetData = useSelector(rulesEngineSelectors.getSnippetData);
|
||||
// const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
||||
// const abilityLookup = useSelector(rulesEngineSelectors.getAbilityLookup);
|
||||
// const dataOriginRefData = useSelector(rulesEngineSelectors.getDataOriginRefData);
|
||||
// const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
||||
const proficiencyBonus = useSelector(
|
||||
rulesEngineSelectors.getProficiencyBonus
|
||||
);
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
|
||||
return (
|
||||
<div className="ct-blessings-detail">
|
||||
{blessings.length ? (
|
||||
<React.Fragment>
|
||||
{blessings.map((blessing) => (
|
||||
<div
|
||||
onClick={(evt: React.MouseEvent) => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(
|
||||
PaneComponentEnum.BLESSING_DETAIL,
|
||||
PaneIdentifierUtils.generateBlessing(blessing.getId())
|
||||
);
|
||||
}}
|
||||
className={"ct-feature-snippet"}
|
||||
key={blessing.getId()}
|
||||
>
|
||||
<div
|
||||
className={`ct-feature-snippet__heading ${
|
||||
theme?.isDarkMode
|
||||
? "ct-feature-snippet__heading--dark-mode"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{blessing.getName()}
|
||||
</div>
|
||||
<div className="ct-feature-snippet__content">
|
||||
<Snippet
|
||||
snippetData={snippetData}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
theme={theme}
|
||||
>
|
||||
{blessing.getDescription()}
|
||||
</Snippet>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div
|
||||
className={`ct-blessings-detail__default ${
|
||||
theme.isDarkMode ? "ct-blessings-detail__default--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
<p>There is nothing here.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default BlessingsDetail;
|
||||
@@ -1,4 +0,0 @@
|
||||
import BlessingsDetail from "./BlessingsDetail";
|
||||
|
||||
export default BlessingsDetail;
|
||||
export { BlessingsDetail };
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardActionArea from "@mui/material/CardActionArea";
|
||||
import CardActions from "@mui/material/CardActions";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { MaxCharactersDialog } from "~/components/MaxCharactersDialog";
|
||||
import { useAuth } from "~/contexts/Authentication";
|
||||
import { useClaimCharacter } from "~/hooks/useClaimCharacter";
|
||||
import backgroundImage from "~/images/claim.png";
|
||||
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
|
||||
|
||||
import {
|
||||
DESKTOP_COMPONENT_START_WIDTH,
|
||||
TABLET_COMPONENT_START_WIDTH,
|
||||
} from "../../config";
|
||||
import ClaimConfirmationDialog from "../WatchTourDialog/ClaimConfirmationDialog";
|
||||
|
||||
const renderButtonContent = (signedIn: boolean) => (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", marginLeft: "10px" }}>
|
||||
{!signedIn && (
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginRight: "5px",
|
||||
textTransform: "none",
|
||||
fontWeight: 400,
|
||||
fontSize: "14px",
|
||||
lineHeight: "16px",
|
||||
}}
|
||||
>
|
||||
Create Account to
|
||||
</Typography>
|
||||
)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
textTransform: "none",
|
||||
fontWeight: 700,
|
||||
fontSize: "14px",
|
||||
lineHeight: "16px",
|
||||
}}
|
||||
>
|
||||
Claim Character
|
||||
</Typography>
|
||||
<ChevronRightIcon />
|
||||
</Box>
|
||||
);
|
||||
|
||||
// z-index of the sidebar controls
|
||||
const zIndex = 60002;
|
||||
|
||||
const createAccountLink = `/create-account?returnUrl=${window.location.pathname}`;
|
||||
|
||||
const ClaimPremadeButton: React.FC = () => {
|
||||
const params = new URLSearchParams(globalThis.location.search);
|
||||
const campaignJoinCode = params.get("campaignJoinCode");
|
||||
const isAssigned = params.get("isAssigned") === "false" ? false : true;
|
||||
|
||||
const [isMaxCharacterMessageOpen, setIsMaxCharacterMessageOpen] =
|
||||
useState(false);
|
||||
|
||||
const envDimensions = useSelector(appEnvSelectors.getDimensions);
|
||||
|
||||
const { characterSlotLimit, activeCharacterCount } = useSelector(
|
||||
appEnvSelectors.getCharacterSlots
|
||||
);
|
||||
// Character slot limit is null for admin accounts
|
||||
const hasOpenSlot =
|
||||
characterSlotLimit === null || activeCharacterCount < characterSlotLimit;
|
||||
|
||||
const [
|
||||
claimCharacter,
|
||||
isClaimingCharacter,
|
||||
isFinishedClaimingCharacter,
|
||||
newCharacterId,
|
||||
campaignId,
|
||||
] = useClaimCharacter({
|
||||
campaignJoinCode,
|
||||
isAssigned,
|
||||
});
|
||||
|
||||
const user = useAuth();
|
||||
const signedIn = !!user;
|
||||
|
||||
const handleClaimButtonClick = () => {
|
||||
hasOpenSlot ? claimCharacter() : setIsMaxCharacterMessageOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{envDimensions.window.width >= TABLET_COMPONENT_START_WIDTH ? (
|
||||
<Card
|
||||
className="claimPremade-button"
|
||||
hidden={isFinishedClaimingCharacter && !!newCharacterId}
|
||||
sx={{
|
||||
zIndex,
|
||||
position: "fixed",
|
||||
right: 30,
|
||||
bottom:
|
||||
envDimensions.window.width >= DESKTOP_COMPONENT_START_WIDTH
|
||||
? 30
|
||||
: 70,
|
||||
width: 300,
|
||||
background: "#551710",
|
||||
borderRadius: "8px",
|
||||
overflow: "visible",
|
||||
boxShadow:
|
||||
"0px 57px 23px rgba(0, 0, 0, 0.01), 0px 32px 19px rgba(0, 0, 0, 0.05), 0px 14px 14px rgba(0, 0, 0, 0.08), 0px 4px 8px rgba(0, 0, 0, 0.09), 0px 0px 0px rgba(0, 0, 0, 0.09)",
|
||||
"&:hover": {
|
||||
"& .claimPremade-buttonContent": {
|
||||
background: "#F5F3EE",
|
||||
boxShadow: "0px -4px 12px rgba(0, 0, 0, 0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardActionArea
|
||||
sx={{ textAlign: "center" }}
|
||||
{...(!signedIn && { href: createAccountLink })}
|
||||
onClick={signedIn ? handleClaimButtonClick : undefined}
|
||||
disabled={isClaimingCharacter}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={backgroundImage}
|
||||
sx={{ position: "relative", top: -20, width: 244, height: 144 }}
|
||||
/>
|
||||
<CardActions
|
||||
className="claimPremade-buttonContent"
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
height: 48,
|
||||
color: "#000000",
|
||||
background: "#FFFFFF",
|
||||
|
||||
borderRadius: "0 0 8px 8px",
|
||||
}}
|
||||
>
|
||||
{renderButtonContent(signedIn)}
|
||||
</CardActions>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
className="claimPremade-button"
|
||||
{...(!signedIn && { href: createAccountLink })}
|
||||
sx={{
|
||||
display:
|
||||
isFinishedClaimingCharacter && !!newCharacterId
|
||||
? "none"
|
||||
: "inline-flex",
|
||||
zIndex,
|
||||
position: "fixed",
|
||||
left: "50%",
|
||||
bottom: 70,
|
||||
width: 300,
|
||||
height: "50px",
|
||||
marginLeft: "-150px",
|
||||
color: "#000000",
|
||||
backgroundColor: "#FFFFFF",
|
||||
boxShadow:
|
||||
"0px 57px 23px rgba(0, 0, 0, 0.01), 0px 32px 19px rgba(0, 0, 0, 0.05), 0px 14px 14px rgba(0, 0, 0, 0.08), 0px 4px 8px rgba(0, 0, 0, 0.09), 0px 0px 0px rgba(0, 0, 0, 0.09)",
|
||||
"&:hover": {
|
||||
backgroundColor: "#F5F3EE",
|
||||
boxShadow:
|
||||
"0px 57px 23px rgba(0, 0, 0, 0.01), 0px 32px 19px rgba(0, 0, 0, 0.05), 0px 14px 14px rgba(0, 0, 0, 0.08), 0px 4px 8px rgba(0, 0, 0, 0.09), 0px 0px 0px rgba(0, 0, 0, 0.09)",
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
}}
|
||||
onClick={signedIn ? handleClaimButtonClick : undefined}
|
||||
disabled={isClaimingCharacter}
|
||||
>
|
||||
{renderButtonContent(signedIn)}
|
||||
</Button>
|
||||
)}
|
||||
<MaxCharactersDialog
|
||||
open={isMaxCharacterMessageOpen}
|
||||
onClose={() => setIsMaxCharacterMessageOpen(false)}
|
||||
useMyCharactersLink
|
||||
/>
|
||||
{isFinishedClaimingCharacter && !!newCharacterId && (
|
||||
<ClaimConfirmationDialog
|
||||
characterId={newCharacterId}
|
||||
campaignId={campaignId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClaimPremadeButton;
|
||||
@@ -1,4 +0,0 @@
|
||||
import ClaimPremadeButton from "./ClaimPremadeButton";
|
||||
|
||||
export default ClaimPremadeButton;
|
||||
export { ClaimPremadeButton as ClaimPremadeDialog };
|
||||
+14
-6
@@ -16,6 +16,7 @@ interface CarouselWindowItem {
|
||||
offset: number;
|
||||
idx: number;
|
||||
key: string;
|
||||
itemKey: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -233,7 +234,7 @@ export default class ComponentCarousel extends React.PureComponent<
|
||||
return posIndex % total;
|
||||
} else {
|
||||
const indexMod = Math.abs(posIndex) % total;
|
||||
return (indexMod === 0 ? 0 : 1) * (total - indexMod);
|
||||
return indexMod === 0 ? 0 : total - indexMod;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -306,6 +307,7 @@ export default class ComponentCarousel extends React.PureComponent<
|
||||
swipedAmount,
|
||||
childrenCount,
|
||||
currentIdx,
|
||||
childByIdxLookup,
|
||||
} = state;
|
||||
|
||||
let currentPosIdx: number = ComponentCarousel.getSwipedItemPosIdx(
|
||||
@@ -324,14 +326,21 @@ export default class ComponentCarousel extends React.PureComponent<
|
||||
i,
|
||||
childrenCount
|
||||
);
|
||||
const child = childByIdxLookup[itemIdx];
|
||||
const itemKey = child?.props?.itemKey;
|
||||
|
||||
// Find existing window item by matching both itemKey AND offset
|
||||
let existingItem = windowItems.find(
|
||||
(windowItem) => windowItem.idx === itemIdx
|
||||
(windowItem) =>
|
||||
windowItem.itemKey === itemKey && windowItem.offset === offset
|
||||
);
|
||||
|
||||
newWindowItems.push({
|
||||
offset,
|
||||
idx: itemIdx,
|
||||
key: existingItem ? existingItem.key : uniqueId(),
|
||||
itemKey: itemKey,
|
||||
// Use itemKey + position index i to ensure uniqueness
|
||||
key: existingItem ? existingItem.key : `${itemKey}-${i}`,
|
||||
});
|
||||
|
||||
offset++;
|
||||
@@ -505,7 +514,7 @@ export default class ComponentCarousel extends React.PureComponent<
|
||||
<div className={classNames.join(" ")} ref={this.placeholderRef}>
|
||||
<TransitionMotion styles={placeholderStyles}>
|
||||
{(interpolatedStyles) => (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{interpolatedStyles.map((config) => {
|
||||
const { PlaceholderComponent, placeholderProps, itemKey } =
|
||||
config.data.child.props;
|
||||
@@ -526,12 +535,11 @@ export default class ComponentCarousel extends React.PureComponent<
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
</div>
|
||||
);
|
||||
//`
|
||||
};
|
||||
|
||||
renderActiveComponent = (): React.ReactNode => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class ConditionsSummary extends React.PureComponent<Props> {
|
||||
const { conditions, theme } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{conditions.map((condition) => (
|
||||
<ConditionName
|
||||
theme={theme}
|
||||
@@ -35,7 +35,7 @@ export default class ConditionsSummary extends React.PureComponent<Props> {
|
||||
condition={condition}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
header: React.ReactNode;
|
||||
extra?: React.ReactNode;
|
||||
}
|
||||
|
||||
+12
-23
@@ -57,14 +57,8 @@ const CoinContent = ({
|
||||
}
|
||||
const { pp, gp, ep, sp, cp } = coin;
|
||||
|
||||
const shouldReadonly =
|
||||
isReadonly ||
|
||||
(containerDefinitionKey ===
|
||||
coinManager.getPartyEquipmentContainerDefinitionKey() &&
|
||||
coinManager.isSharingTurnedDeleteOnly());
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-currency-pane__currencies">
|
||||
<CurrencyPaneCurrencyRow
|
||||
name="Platinum (pp)"
|
||||
@@ -77,7 +71,7 @@ const CoinContent = ({
|
||||
value={pp}
|
||||
coinType={Constants.CoinTypeEnum.pp}
|
||||
conversion="1 pp = 10 gp"
|
||||
isReadonly={shouldReadonly}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
<CurrencyPaneCurrencyRow
|
||||
name="Gold (gp)"
|
||||
@@ -87,7 +81,7 @@ const CoinContent = ({
|
||||
onError={(errorType) => handleCurrencyChangeError("Gold", errorType)}
|
||||
value={gp}
|
||||
coinType={Constants.CoinTypeEnum.gp}
|
||||
isReadonly={shouldReadonly}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
<CurrencyPaneCurrencyRow
|
||||
name="Electrum (ep)"
|
||||
@@ -100,7 +94,7 @@ const CoinContent = ({
|
||||
value={ep}
|
||||
conversion="1 gp = 2 ep"
|
||||
coinType={Constants.CoinTypeEnum.ep}
|
||||
isReadonly={shouldReadonly}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
<CurrencyPaneCurrencyRow
|
||||
name="Silver (sp)"
|
||||
@@ -113,7 +107,7 @@ const CoinContent = ({
|
||||
value={sp}
|
||||
conversion="1 gp = 10 sp"
|
||||
coinType={Constants.CoinTypeEnum.sp}
|
||||
isReadonly={shouldReadonly}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
<CurrencyPaneCurrencyRow
|
||||
name="Copper (cp)"
|
||||
@@ -125,21 +119,16 @@ const CoinContent = ({
|
||||
}
|
||||
value={cp}
|
||||
conversion="1 gp = 100 cp"
|
||||
isReadonly={shouldReadonly}
|
||||
isReadonly={isReadonly}
|
||||
coinType={Constants.CoinTypeEnum.cp}
|
||||
/>
|
||||
</div>
|
||||
{(coinManager.isSharingTurnedOn() ||
|
||||
!coinManager.isSharedContainerDefinitionKey(
|
||||
containerDefinitionKey
|
||||
)) && (
|
||||
<CurrencyPaneAdjuster
|
||||
onAdjust={handleCurrencyAdjust}
|
||||
isReadonly={isReadonly}
|
||||
containerDefinitionKey={containerDefinitionKey}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
<CurrencyPaneAdjuster
|
||||
onAdjust={handleCurrencyAdjust}
|
||||
isReadonly={isReadonly}
|
||||
containerDefinitionKey={containerDefinitionKey}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class DefensesSummary extends React.PureComponent<Props> {
|
||||
const { theme } = this.props;
|
||||
let orderedDefenses = orderBy(defenses, (group) => group.name);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{orderedDefenses.map((defense: DefenseAdjustmentGroup, idx) => (
|
||||
<span
|
||||
className={`ct-defenses-summary__defense ${
|
||||
@@ -61,7 +61,7 @@ export default class DefensesSummary extends React.PureComponent<Props> {
|
||||
</Tooltip>
|
||||
</span>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
//`
|
||||
};
|
||||
@@ -133,7 +133,7 @@ export default class DefensesSummary extends React.PureComponent<Props> {
|
||||
vulnerabilities.length > 0 &&
|
||||
immunities.length > 0;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{this.renderDamageAdjustmentGroup(
|
||||
DEFENSE_GROUP.RESISTANCE,
|
||||
"Resistances",
|
||||
@@ -155,7 +155,7 @@ export default class DefensesSummary extends React.PureComponent<Props> {
|
||||
isSingleLine,
|
||||
theme
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import {
|
||||
CharacterCurrencyContract,
|
||||
Constants,
|
||||
FormatUtils,
|
||||
RuleDataUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
|
||||
import CurrencyButton from "../CurrencyButton";
|
||||
|
||||
interface Props {
|
||||
currencies: CharacterCurrencyContract | null;
|
||||
weight: number;
|
||||
weightSpeedType: Constants.WeightSpeedTypeEnum;
|
||||
onCurrencyClick?: (evt: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onWeightClick?: () => void;
|
||||
onCampaignClick?: () => void;
|
||||
onManageClick?: () => void;
|
||||
enableManage: boolean;
|
||||
isReadonly: boolean;
|
||||
isDarkMode: boolean;
|
||||
shouldShowPartyInventory: boolean;
|
||||
campaignName: string | null;
|
||||
}
|
||||
export default class EquipmentOverview extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
enableManage: true,
|
||||
isReadonly: false,
|
||||
};
|
||||
|
||||
handleCurrencyClick = (evt: React.MouseEvent | React.KeyboardEvent): void => {
|
||||
const { onCurrencyClick } = this.props;
|
||||
|
||||
if (onCurrencyClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onCurrencyClick(evt);
|
||||
}
|
||||
};
|
||||
|
||||
handleCampaignClick = (evt: React.MouseEvent | React.KeyboardEvent): void => {
|
||||
const { onCampaignClick } = this.props;
|
||||
|
||||
if (onCampaignClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onCampaignClick();
|
||||
}
|
||||
};
|
||||
|
||||
handleWeightClick = (evt: React.MouseEvent | React.KeyboardEvent): void => {
|
||||
const { onWeightClick } = this.props;
|
||||
|
||||
if (onWeightClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onWeightClick();
|
||||
}
|
||||
};
|
||||
|
||||
handleManageClick = (evt: React.MouseEvent | React.KeyboardEvent): void => {
|
||||
const { onManageClick } = this.props;
|
||||
|
||||
if (onManageClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onManageClick();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
weight,
|
||||
weightSpeedType,
|
||||
enableManage,
|
||||
isReadonly,
|
||||
isDarkMode,
|
||||
shouldShowPartyInventory,
|
||||
campaignName,
|
||||
currencies,
|
||||
} = this.props;
|
||||
|
||||
let weightSpeedLabel =
|
||||
RuleDataUtils.getWeightSpeedTypeLabel(weightSpeedType);
|
||||
|
||||
return (
|
||||
// TODO: add a min height?
|
||||
<div className="ct-equipment-overview">
|
||||
{!shouldShowPartyInventory ? (
|
||||
<div
|
||||
role="button"
|
||||
className="ct-equipment-overview__weight"
|
||||
onClick={this.handleWeightClick}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
this.handleWeightClick(evt);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="ct-equipment-overview__weight-carried">
|
||||
<span
|
||||
className={`ct-equipment-overview__weight-carried-label ${
|
||||
isDarkMode
|
||||
? "ct-equipment-overview__weight-carried--dark-mode"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
Weight Carried:
|
||||
</span>
|
||||
<span
|
||||
className={`ct-equipment-overview__weight-carried-amount ${
|
||||
isDarkMode
|
||||
? "ct-equipment-overview__weight-carried--dark-mode"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<NumberDisplay
|
||||
data-testid="weight-carried-number"
|
||||
type="weightInLb"
|
||||
number={weight}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`ct-equipment-overview__weight-speed ct-equipment-overview__weight-speed--${FormatUtils.slugify(
|
||||
weightSpeedLabel
|
||||
)}`}
|
||||
>
|
||||
{weightSpeedLabel}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
role="button"
|
||||
className="ct-equipment-overview__weight"
|
||||
onClick={this.handleCampaignClick}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
this.handleCampaignClick(evt);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="ct-equipment-overview__weight-carried">
|
||||
<span
|
||||
className={`ct-equipment-overview__weight-carried-label ${
|
||||
isDarkMode
|
||||
? "ct-equipment-overview__weight-carried--dark-mode"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
CAMPAIGN:
|
||||
<span
|
||||
className={`ct-equipment-overview__weight-carried-amount ${
|
||||
isDarkMode
|
||||
? "ct-equipment-overview__weight-carried--dark-mode"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{campaignName || "N/A"}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ct-equipment-overview__sep" />
|
||||
<CurrencyButton
|
||||
handleCurrencyClick={this.handleCurrencyClick}
|
||||
coin={currencies}
|
||||
isDarkMode={isDarkMode}
|
||||
shouldShowPartyInventory={shouldShowPartyInventory}
|
||||
/>
|
||||
{enableManage && !isReadonly && (
|
||||
<div
|
||||
role="button"
|
||||
className="ct-equipment-overview__manage"
|
||||
onClick={this.handleManageClick}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
this.handleManageClick(evt);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Manage Equipment" isDarkMode={isDarkMode}>
|
||||
<span className="ct-equipment-overview__manage-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import EquipmentOverview from "./EquipmentOverview";
|
||||
|
||||
export default EquipmentOverview;
|
||||
export { EquipmentOverview };
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class ExtrasFilterAdvancedFilter extends React.PureComponent {
|
||||
export default class ExtrasFilterAdvancedFilter extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-extras-filter__adv-filter">{this.props.children}</div>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class ExtrasFilterAdvancedFilterLabel extends React.PureComponent {
|
||||
export default class ExtrasFilterAdvancedFilterLabel extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-extras-filter__adv-filter-label">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class ExtrasFilterAdvancedFilterOption extends React.PureComponent {
|
||||
export default class ExtrasFilterAdvancedFilterOption extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-extras-filter__adv-filter-option">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class ExtrasFilterAdvancedFilterOptions extends React.PureComponent {
|
||||
export default class ExtrasFilterAdvancedFilterOptions extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-extras-filter__adv-filter-options">
|
||||
|
||||
@@ -50,7 +50,7 @@ export const FeatsDetail: React.FC<Props> = ({
|
||||
return (
|
||||
<div className="ct-feats-detail">
|
||||
{currentFeats.length ? (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{currentFeats.map((feat) => (
|
||||
<FeatFeatureSnippet
|
||||
key={feat.getId()}
|
||||
@@ -69,7 +69,7 @@ export const FeatsDetail: React.FC<Props> = ({
|
||||
theme={theme}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`ct-feats-detail__default ${
|
||||
|
||||
+1
@@ -73,6 +73,7 @@ export default class FeatureSnippetAction extends React.PureComponent<Props> {
|
||||
case Constants.ActivationTypeEnum.ACTION:
|
||||
case Constants.ActivationTypeEnum.REACTION:
|
||||
case Constants.ActivationTypeEnum.BONUS_ACTION:
|
||||
case Constants.ActivationTypeEnum.SPECIAL:
|
||||
activationDisplay = ActivationUtils.renderActivation(
|
||||
activation,
|
||||
ruleData
|
||||
|
||||
+6
-1
@@ -5,6 +5,7 @@ import {
|
||||
BaseFeat,
|
||||
CharacterTheme,
|
||||
Choice,
|
||||
ChoiceUtils,
|
||||
ClassDefinitionContract,
|
||||
Constants,
|
||||
DataOriginBaseAction,
|
||||
@@ -112,7 +113,11 @@ export default class FeatureSnippetChoices extends React.PureComponent<Props> {
|
||||
abilityLookup={abilityLookup}
|
||||
dataOriginRefData={dataOriginRefData}
|
||||
sourceDataLookup={sourceDataLookup}
|
||||
showDescription={showDescription}
|
||||
showDescription={
|
||||
ChoiceUtils.getItemDefinitionKey(choice)
|
||||
? false
|
||||
: showDescription
|
||||
}
|
||||
isInteractive={isInteractive}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
theme={theme}
|
||||
|
||||
+1
-5
@@ -53,11 +53,7 @@ export default class FeatureSnippetInfusionChoices extends React.PureComponent<P
|
||||
const simulatedInfusion =
|
||||
KnownInfusionUtils.getSimulatedInfusion(knownInfusion);
|
||||
if (simulatedInfusion !== null) {
|
||||
nameNode = (
|
||||
<React.Fragment>
|
||||
{InfusionUtils.getName(simulatedInfusion)}
|
||||
</React.Fragment>
|
||||
);
|
||||
nameNode = <>{InfusionUtils.getName(simulatedInfusion)}</>;
|
||||
onClick = this.handleInfusionChoiceClick.bind(
|
||||
this,
|
||||
infusionChoice
|
||||
|
||||
+2
-2
@@ -161,7 +161,7 @@ export default class FeatureSnippetLimitedUse extends React.PureComponent<Props>
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div
|
||||
className={`ct-feature-snippet__limited-use ${
|
||||
theme.isDarkMode ? "ct-feature-snippet__limited-use--dark-mode" : ""
|
||||
@@ -180,7 +180,7 @@ export default class FeatureSnippetLimitedUse extends React.PureComponent<Props>
|
||||
{extraNode}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ export class Infusions extends React.PureComponent<Props, State> {
|
||||
const { infusionChoices, ruleData, theme } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{infusionChoices.map((infusionChoice) => {
|
||||
const knownInfusion =
|
||||
InfusionChoiceUtils.getKnownInfusion(infusionChoice);
|
||||
@@ -279,7 +279,7 @@ export class Infusions extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import { BuilderLinkButton } from "../../../Shared/components/common/LinkButton";
|
||||
|
||||
@@ -11,6 +12,10 @@ export const InvalidCharacter: FC<InvalidCharacterProps> = ({
|
||||
builderUrl,
|
||||
isReadonly,
|
||||
}) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
const sendToNonVttBuilderUrl = builderUrl.replace("view=vtt", "");
|
||||
|
||||
return (
|
||||
<div className="ct-invalid-character">
|
||||
<h2>Character Not Ready!</h2>
|
||||
@@ -30,9 +35,10 @@ export const InvalidCharacter: FC<InvalidCharacterProps> = ({
|
||||
|
||||
<p>
|
||||
<BuilderLinkButton
|
||||
url={builderUrl}
|
||||
url={isVttView ? sendToNonVttBuilderUrl : builderUrl}
|
||||
className="ct-invalid-character__button"
|
||||
size="oversized"
|
||||
target={isVttView ? "_blank" : undefined}
|
||||
>
|
||||
Character Builder
|
||||
</BuilderLinkButton>
|
||||
|
||||
@@ -287,9 +287,11 @@ export default class InventoryFilter extends React.PureComponent<Props, State> {
|
||||
|
||||
handleFiltersClear = (): void => {
|
||||
const inventory = this.getInventory(this.props);
|
||||
const partyInventory = this.getPartyInventory(this.props);
|
||||
this.setState(
|
||||
{
|
||||
filteredInventory: inventory,
|
||||
filteredPartyInventory: partyInventory,
|
||||
filterQuery: "",
|
||||
filterTypes: [],
|
||||
filterRarities: [],
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class InventoryFilterAdvancedFilter extends React.PureComponent {
|
||||
export default class InventoryFilterAdvancedFilter extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-inventory-filter__adv-filter">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class InventoryFilterAdvancedFilterLabel extends React.PureComponent {
|
||||
export default class InventoryFilterAdvancedFilterLabel extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-inventory-filter__adv-filter-label">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class InventoryFilterAdvancedFilterOption extends React.PureComponent {
|
||||
export default class InventoryFilterAdvancedFilterOption extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-inventory-filter__adv-filter-option">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class InventoryFilterAdvancedFilterOptions extends React.PureComponent {
|
||||
export default class InventoryFilterAdvancedFilterOptions extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-inventory-filter__adv-filter-options">
|
||||
|
||||
+2
-2
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
interface Props {
|
||||
proficiencyBonus: number;
|
||||
@@ -31,7 +31,7 @@ export default class ProficiencyBonusBox extends React.PureComponent<Props> {
|
||||
return (
|
||||
<section className="ct-proficiency-bonus-box" onClick={this.handleClick}>
|
||||
<BoxBackground StyleComponent={BeveledBoxSvg94x89} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Proficiency Bonus</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Proficiency Bonus</h2>
|
||||
<div
|
||||
className={`ct-proficiency-bonus-box__heading ${
|
||||
theme.isDarkMode
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class ProficiencyGroups extends React.PureComponent<Props> {
|
||||
let itemsNode: React.ReactNode = "None";
|
||||
if (group.modifierGroups.length) {
|
||||
itemsNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{group.modifierGroups.map((modifierGroup, idx) => {
|
||||
let title: string = modifierGroup.sources.join(", ");
|
||||
return (
|
||||
@@ -59,7 +59,7 @@ export default class ProficiencyGroups extends React.PureComponent<Props> {
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -11,6 +10,8 @@ import {
|
||||
ProficiencyGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { StyleSizeTypeEnum } from "../../../Shared/reducers/appEnv";
|
||||
import { AppEnvDimensionsState } from "../../../Shared/stores/typings";
|
||||
import ProficiencyGroups from "../ProficiencyGroups";
|
||||
@@ -36,7 +37,9 @@ export default class ProficiencyGroupsBox extends React.PureComponent<Props> {
|
||||
return (
|
||||
<section className="ct-proficiency-groups-box">
|
||||
<BoxBackground StyleComponent={BoxBackgroundComponent} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Proficiencies and Languages</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>
|
||||
Proficiencies and Languages
|
||||
</h2>
|
||||
<ProficiencyGroups
|
||||
proficiencyGroups={proficiencyGroups}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { orderBy } from "lodash";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
@@ -18,7 +17,9 @@ import {
|
||||
RuleData,
|
||||
SituationalSavingThrowInfoLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import DiceAdjustmentSummary from "../../../Shared/components/DiceAdjustmentSummary";
|
||||
import { StyleSizeTypeEnum } from "../../../Shared/reducers/appEnv";
|
||||
@@ -37,7 +38,7 @@ interface Props {
|
||||
dimensions: AppEnvDimensionsState;
|
||||
theme: CharacterTheme;
|
||||
diceEnabled?: boolean;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
}
|
||||
|
||||
const sortDiceAdjustments = (
|
||||
@@ -276,7 +277,7 @@ export default function SavingThrowsBox({
|
||||
return (
|
||||
<section className="ct-saving-throws-box" onClick={handleClick}>
|
||||
<BoxBackground StyleComponent={BoxBackgroundComponent} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Saving Throws</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Saving Throws</h2>
|
||||
<div className="ct-saving-throws-box__abilities">
|
||||
<SavingThrowsSummary
|
||||
abilities={abilities}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
BoxBackground,
|
||||
ThemedSenseRowBoxSvg,
|
||||
ThemedSenseRowSmallBoxSvg,
|
||||
ThemedSenseRowMinimalSvg,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
@@ -14,13 +16,14 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { TypeScriptUtils } from "../../../Shared/utils";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
passivePerception: number;
|
||||
passiveInvestigation: number;
|
||||
passiveInsight: number;
|
||||
senses: SenseInfo;
|
||||
rowStyle: "small" | "normal";
|
||||
rowStyle: "small" | "normal" | "minimal";
|
||||
onClick?: () => void;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
@@ -53,8 +56,6 @@ export default class Senses extends React.PureComponent<Props> {
|
||||
};
|
||||
|
||||
renderSummaryInfo = (): React.ReactNode => {
|
||||
const { theme } = this.props;
|
||||
|
||||
let senseKeys: Array<Constants.SenseTypeEnum> = [
|
||||
Constants.SenseTypeEnum.BLINDSIGHT,
|
||||
Constants.SenseTypeEnum.DARKVISION,
|
||||
@@ -66,24 +67,17 @@ export default class Senses extends React.PureComponent<Props> {
|
||||
.map((senseKey) => this.getSenseSummary(senseKey))
|
||||
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
||||
|
||||
let summaryClasses: Array<string> = ["ct-senses__summary"];
|
||||
if (!senseSummaries.length) {
|
||||
summaryClasses.push("ct-senses__summary--empty");
|
||||
}
|
||||
if (theme?.isDarkMode) {
|
||||
summaryClasses.push("ct-senses__summary--dark-mode");
|
||||
}
|
||||
|
||||
if (senseSummaries.length) {
|
||||
return (
|
||||
<div className={summaryClasses.join(" ")}>
|
||||
{senseSummaries.join(", ")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={summaryClasses.join(" ")}>Additional Sense Types</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.summary,
|
||||
!senseSummaries.length && styles.summaryEmpty,
|
||||
])}
|
||||
>
|
||||
{!senseSummaries.length
|
||||
? "Additional Sense Types"
|
||||
: senseSummaries.join(", ")}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -96,84 +90,140 @@ export default class Senses extends React.PureComponent<Props> {
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
let isMinimal = false;
|
||||
let StyleComponent: React.ComponentType<any> = ThemedSenseRowBoxSvg;
|
||||
if (rowStyle === "small") {
|
||||
StyleComponent = ThemedSenseRowSmallBoxSvg;
|
||||
} else if (rowStyle === "minimal") {
|
||||
StyleComponent = ThemedSenseRowMinimalSvg;
|
||||
isMinimal = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-senses" onClick={this.handleClick}>
|
||||
<div className="ct-senses__callouts">
|
||||
<div className="ct-senses__callout">
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`ct-senses__callout-value ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-value--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
{passivePerception}
|
||||
<div className={styles.senses} onClick={this.handleClick}>
|
||||
{isMinimal ? (
|
||||
<div className={styles.minimalRow}>
|
||||
<div className={styles.minimalItem}>
|
||||
<div className={styles.minimalCallout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>{passivePerception}</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.minimalCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Perception
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`ct-senses__callout-label ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-label--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
Passive Perception
|
||||
<div className={styles.minimalItem}>
|
||||
<div className={styles.minimalCallout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>
|
||||
{passiveInvestigation}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.minimalCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Investigation
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.minimalItem}>
|
||||
<div className={styles.minimalCallout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>{passiveInsight}</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.minimalCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Insight
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ct-senses__callout">
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`ct-senses__callout-value ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-value--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
{passiveInvestigation}
|
||||
) : (
|
||||
<div>
|
||||
<div className={styles.callout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>{passivePerception}</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.regularCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Passive Perception
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`ct-senses__callout-label ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-label--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
Passive Investigation
|
||||
<div className={styles.callout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>{passiveInvestigation}</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.regularCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Passive Investigation
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.callout}>
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.calloutValue}>{passiveInsight}</div>
|
||||
<div
|
||||
className={clsx([
|
||||
styles.calloutLabel,
|
||||
styles.regularCalloutLabel,
|
||||
])}
|
||||
>
|
||||
Passive Insight
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ct-senses__callout">
|
||||
<BoxBackground
|
||||
StyleComponent={StyleComponent}
|
||||
theme={{
|
||||
...theme,
|
||||
themeColor: `${theme?.themeColor}80`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`ct-senses__callout-value ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-value--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
{passiveInsight}
|
||||
</div>
|
||||
<div
|
||||
className={`ct-senses__callout-label ${
|
||||
theme?.isDarkMode ? "ct-senses__callout-label--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
Passive Insight
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.renderSummaryInfo()}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -11,6 +10,8 @@ import {
|
||||
SenseInfo,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { StyleSizeTypeEnum } from "../../../Shared/reducers/appEnv";
|
||||
import { AppEnvDimensionsState } from "../../../Shared/stores/typings";
|
||||
import Senses from "../Senses";
|
||||
@@ -49,7 +50,7 @@ export default class SensesBox extends React.PureComponent<Props> {
|
||||
return (
|
||||
<section className="ct-senses-box">
|
||||
<BoxBackground StyleComponent={BoxBackgroundComponent} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Senses</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Senses</h2>
|
||||
<Senses
|
||||
senses={senses}
|
||||
passiveInsight={passiveInsight}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
interface Props {
|
||||
context: string;
|
||||
isReadonly: boolean;
|
||||
}
|
||||
|
||||
const SettingsButton: React.FC<Props> = ({ context, isReadonly }) => {
|
||||
const {
|
||||
pane: { paneHistoryPush },
|
||||
} = useSidebar();
|
||||
return !isReadonly ? (
|
||||
<Button
|
||||
onClick={(evt) => {
|
||||
paneHistoryPush(PaneComponentEnum.SETTINGS, {
|
||||
context,
|
||||
});
|
||||
}}
|
||||
variant="text"
|
||||
startIcon={<SettingsIcon />}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default SettingsButton;
|
||||
@@ -1,4 +0,0 @@
|
||||
import SettingsButton from "./SettingsButton";
|
||||
|
||||
export default SettingsButton;
|
||||
export { SettingsButton };
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
@@ -19,9 +18,12 @@ import {
|
||||
ValueLookup,
|
||||
ValueUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { RollType, DiceTools, IRollContext } 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";
|
||||
|
||||
interface Props {
|
||||
skills: Array<Skill>;
|
||||
@@ -33,7 +35,7 @@ interface Props {
|
||||
diceEnabled: boolean;
|
||||
theme: CharacterTheme;
|
||||
ruleData: RuleData;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
}
|
||||
class Skills extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
@@ -87,9 +89,6 @@ class Skills extends React.PureComponent<Props> {
|
||||
const { customSkills, ruleData, diceEnabled, theme, rollContext } =
|
||||
this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
return customSkills.map((skill) => {
|
||||
const modifier = SkillUtils.getModifier(skill);
|
||||
const statName = RuleDataUtils.getAbilityShortName(
|
||||
@@ -150,26 +149,17 @@ class Skills extends React.PureComponent<Props> {
|
||||
"--"
|
||||
) : (
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceTools.CustomD20(
|
||||
SkillUtils.getModifier(skill)!
|
||||
)}
|
||||
rollType={RollType.Check}
|
||||
rollAction={name ?? "UNKNOWN"}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
diceNotation={`1d20${
|
||||
modifier > 0 ? `+${modifier}` : modifier !== 0 ? modifier : ""
|
||||
}`}
|
||||
rollType={RollTypes.Check}
|
||||
action={name ?? "UNKNOWN"}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
<NumberDisplay
|
||||
number={modifier}
|
||||
type="signed"
|
||||
/>
|
||||
<NumberDisplay number={modifier} type="signed" />
|
||||
</DigitalDiceWrapper>
|
||||
)}
|
||||
</div>
|
||||
@@ -189,9 +179,6 @@ class Skills extends React.PureComponent<Props> {
|
||||
rollContext,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
let orderedSkills = orderBy(skills, (skill) => SkillUtils.getName(skill));
|
||||
|
||||
return (
|
||||
@@ -203,7 +190,7 @@ class Skills extends React.PureComponent<Props> {
|
||||
>
|
||||
<div className="ct-skills__header" role="row">
|
||||
<div className="ct-skills__col--proficiency" role="columnheader">
|
||||
<span style={visuallyHidden}>Proficiency</span>
|
||||
<span className={a11yStyles.screenreaderOnly}>Proficiency</span>
|
||||
<abbr
|
||||
aria-hidden="true"
|
||||
title="Proficiency"
|
||||
@@ -215,7 +202,7 @@ class Skills extends React.PureComponent<Props> {
|
||||
</abbr>
|
||||
</div>
|
||||
<div className="ct-skills__col--stat" role="columnheader">
|
||||
<span style={visuallyHidden}>Modifier</span>
|
||||
<span className={a11yStyles.screenreaderOnly}>Modifier</span>
|
||||
<abbr
|
||||
aria-hidden="true"
|
||||
title="Modifier"
|
||||
@@ -245,7 +232,11 @@ class Skills extends React.PureComponent<Props> {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ct-skills__list" role="rowgroup">
|
||||
<div
|
||||
className="ct-skills__list"
|
||||
role="rowgroup"
|
||||
data-scrollable-container="true"
|
||||
>
|
||||
{orderedSkills.map((skill) => {
|
||||
let valueTypes: Array<Constants.AdjustmentTypeEnum> = [
|
||||
Constants.AdjustmentTypeEnum.SKILL_STAT_OVERRIDE,
|
||||
@@ -402,27 +393,25 @@ class Skills extends React.PureComponent<Props> {
|
||||
"--"
|
||||
) : (
|
||||
<DigitalDiceWrapper
|
||||
diceNotation={DiceTools.CustomD20(
|
||||
SkillUtils.getModifier(skill)!
|
||||
)}
|
||||
rollType={RollType.Check}
|
||||
rollAction={name ? name : "UNKNOWN"}
|
||||
diceEnabled={diceEnabled}
|
||||
themeColor={theme.themeColor}
|
||||
rollContext={rollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
diceNotation={`1d20${
|
||||
modifier > 0
|
||||
? `+${modifier}`
|
||||
: modifier !== 0
|
||||
? modifier
|
||||
: ""
|
||||
}`}
|
||||
rollType={RollTypes.Check}
|
||||
action={name ? name : "UNKNOWN"}
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(rollContext.entityId)}
|
||||
entityType={String(rollContext.entityType)}
|
||||
name={String(rollContext.name)}
|
||||
>
|
||||
<NumberDisplay
|
||||
number={modifier}
|
||||
type="signed"
|
||||
isModified={hasModifiedValue}
|
||||
/>
|
||||
/>
|
||||
</DigitalDiceWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -13,7 +12,9 @@ import {
|
||||
Skill,
|
||||
ValueLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { StyleSizeTypeEnum } from "../../../Shared/reducers/appEnv";
|
||||
import { AppEnvDimensionsState } from "../../../Shared/stores/typings";
|
||||
@@ -30,7 +31,7 @@ interface Props {
|
||||
theme: CharacterTheme;
|
||||
diceEnabled: boolean;
|
||||
ruleData: RuleData;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
}
|
||||
export default class SkillsBox extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
@@ -63,7 +64,7 @@ export default class SkillsBox extends React.PureComponent<Props> {
|
||||
return (
|
||||
<section className="ct-skills-box">
|
||||
<BoxBackground StyleComponent={BoxBackgroundComponent} theme={theme} />
|
||||
<h2 style={visuallyHidden}>Skills</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Skills</h2>
|
||||
<Skills
|
||||
skills={skills}
|
||||
customSkills={customSkills}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
interface Props {
|
||||
speeds: SpeedInfo;
|
||||
@@ -47,7 +47,7 @@ export default class SpeedBox extends React.PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<section className="ct-speed-box" onClick={this.handleSpeedsClick}>
|
||||
<h2 style={visuallyHidden}>Speed</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Speed</h2>
|
||||
<BoxBackground StyleComponent={BeveledBoxSvg94x89} theme={theme} />
|
||||
<div
|
||||
className={`ct-speed-box__heading ${
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class SpellsFilterAdvancedFilter extends React.PureComponent {
|
||||
export default class SpellsFilterAdvancedFilter extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-spells-filter__adv-filter">{this.props.children}</div>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class SpellsFilterAdvancedFilterLabel extends React.PureComponent {
|
||||
export default class SpellsFilterAdvancedFilterLabel extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-spells-filter__adv-filter-label">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class SpellsFilterAdvancedFilterOption extends React.PureComponent {
|
||||
export default class SpellsFilterAdvancedFilterOption extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-spells-filter__adv-filter-option">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class SpellsFilterAdvancedFilterOptions extends React.PureComponent {
|
||||
export default class SpellsFilterAdvancedFilterOptions extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-spells-filter__adv-filter-options">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
import { FormatUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
name?: string;
|
||||
className: string;
|
||||
}
|
||||
export default class SubsectionMobile extends React.PureComponent<Props> {
|
||||
export default class SubsectionMobile extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
import { FormatUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
name?: string;
|
||||
className: string;
|
||||
}
|
||||
export default class SubsectionTablet extends React.PureComponent<Props> {
|
||||
export default class SubsectionTablet extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
className: string;
|
||||
}
|
||||
export default class Subsections extends React.PureComponent<Props> {
|
||||
export default class Subsections extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
import { ManageIcon } from "@dndbeyond/character-components/es";
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import MobileDivider from "../MobileDivider";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
header: React.ReactNode;
|
||||
onHeaderClick?: () => void;
|
||||
className: string;
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
traitKey: string;
|
||||
label?: string;
|
||||
fallback: string;
|
||||
onClick?: (traitKey: string) => void;
|
||||
onClick?: (traitKey: Constants.TraitTypeEnum) => void;
|
||||
}
|
||||
export default class TraitContent extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
@@ -23,7 +23,7 @@ export default class TraitContent extends React.PureComponent<Props> {
|
||||
if (onClick) {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
onClick(traitKey);
|
||||
onClick(traitKey as Constants.TraitTypeEnum);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Link from "@mui/material/Link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import characterGalleryImg from "~/images/character-gallery.png";
|
||||
import characterSheetImg from "~/images/character-sheet.png";
|
||||
import tadaEmoji from "~/images/tada-emoji.svg?url";
|
||||
|
||||
type Props = {
|
||||
characterId: number;
|
||||
campaignId?: number | null;
|
||||
};
|
||||
|
||||
export const ClaimConfirmationDialog: React.FC<Props> = ({
|
||||
characterId,
|
||||
campaignId,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(true);
|
||||
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
const { isDarkMode } = theme;
|
||||
|
||||
const close = () => {
|
||||
window.location.href = `/characters/${characterId}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
characterId && setIsOpen(true);
|
||||
}, [characterId]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={close}
|
||||
open={isOpen}
|
||||
fullWidth={true}
|
||||
sx={{
|
||||
"& .MuiPaper-root": {
|
||||
maxWidth: "644px",
|
||||
backgroundColor: isDarkMode ? "#374045" : "white",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle
|
||||
sx={{
|
||||
m: 0,
|
||||
p: "24px 32px 16px",
|
||||
fontSize: "18px",
|
||||
fontWeight: 700,
|
||||
color: isDarkMode ? "white" : "#12181C",
|
||||
}}
|
||||
>
|
||||
<Box component="img" src={tadaEmoji} alt="Ta-da emoji" />{" "}
|
||||
Congratulations!
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={close}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: isDarkMode
|
||||
? "rgba(255, 255, 255, 0.54)"
|
||||
: "rgba(0, 0, 0, 0.54)",
|
||||
"&:hover": {
|
||||
backgroundColor: isDarkMode
|
||||
? "rgba(220, 223, 225, 0.08)"
|
||||
: "#ECEDEE",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ m: 0, p: "0 24px 24px" }}>
|
||||
<Grid container justifyContent="center">
|
||||
<DialogContentText
|
||||
textAlign={"center"}
|
||||
sx={{ color: isDarkMode ? "white" : "#12181C" }}
|
||||
>
|
||||
Your character has been claimed and is now available:
|
||||
</DialogContentText>
|
||||
<Grid item container spacing={2}>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
sm={6}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
sx={{ width: "100%" }}
|
||||
src={characterGalleryImg}
|
||||
alt="Premade character gallery"
|
||||
></Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
href="/characters"
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "230px",
|
||||
marginTop: "-20px",
|
||||
|
||||
color: "#374045",
|
||||
fontSize: "12px",
|
||||
lineHeight: "15px",
|
||||
|
||||
backgroundColor: "#fff",
|
||||
border: "2px solid #3298ED",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "none",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
|
||||
backgroundColor: "#232B2F",
|
||||
border: "2px solid #3298ED",
|
||||
boxShadow: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Go to my characters{" "}
|
||||
<ArrowForwardIosIcon
|
||||
sx={{ position: "absolute", right: "8px", height: "14px" }}
|
||||
/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
sm={6}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
src={characterSheetImg}
|
||||
alt="Sample character sheet"
|
||||
></Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
href={`/characters/${characterId}`}
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "230px",
|
||||
marginTop: "-20px",
|
||||
|
||||
color: "#fff",
|
||||
fontSize: "12px",
|
||||
lineHeight: "15px",
|
||||
|
||||
backgroundColor: "#3298ED",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "none",
|
||||
"&:hover": {
|
||||
color: "#fff",
|
||||
|
||||
backgroundColor: "#53ADF0",
|
||||
boxShadow: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
View your character{" "}
|
||||
<ArrowForwardIosIcon
|
||||
sx={{ position: "absolute", right: "8px", height: "14px" }}
|
||||
/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{campaignId && (
|
||||
<Link
|
||||
href={`/campaigns/${campaignId}`}
|
||||
variant="button"
|
||||
underline="none"
|
||||
sx={{ marginTop: "20px", color: "#3298ED" }}
|
||||
>
|
||||
or return to your campaign
|
||||
</Link>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClaimConfirmationDialog;
|
||||
@@ -1,222 +0,0 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import VideocamIcon from "@mui/icons-material/Videocam";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { MaxCharactersDialog } from "~/components/MaxCharactersDialog";
|
||||
import { useAuth } from "~/contexts/Authentication";
|
||||
import { useClaimCharacter } from "~/hooks/useClaimCharacter";
|
||||
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
|
||||
|
||||
import ClaimConfirmationDialog from "./ClaimConfirmationDialog";
|
||||
|
||||
export const WatchTourDialog: React.FC = () => {
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
const { isDarkMode } = theme;
|
||||
|
||||
const params = new URLSearchParams(globalThis.location.search);
|
||||
const campaignJoinCode = params.get("campaignJoinCode");
|
||||
const isAssigned = params.get("isAssigned") === "false" ? false : true;
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const { characterSlotLimit, activeCharacterCount } = useSelector(
|
||||
appEnvSelectors.getCharacterSlots
|
||||
);
|
||||
// Character slot limit is null for admin accounts
|
||||
const hasOpenSlot =
|
||||
characterSlotLimit === null || activeCharacterCount < characterSlotLimit;
|
||||
|
||||
const [isMaxCharacterMessageOpen, setIsMaxCharacterMessageOpen] =
|
||||
useState(false);
|
||||
|
||||
const user = useAuth();
|
||||
const signedIn = !!user;
|
||||
|
||||
const signupLink = `/create-account?returnUrl=${window.location.pathname}`;
|
||||
|
||||
const [
|
||||
claimCharacter,
|
||||
isClaimingCharacter,
|
||||
isFinishedClaimingCharacter,
|
||||
newCharacterId,
|
||||
campaignId,
|
||||
] = useClaimCharacter({
|
||||
campaignJoinCode,
|
||||
isAssigned,
|
||||
});
|
||||
|
||||
const open = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
const close = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleClaim = () => {
|
||||
if (hasOpenSlot) {
|
||||
claimCharacter();
|
||||
} else {
|
||||
setIsMaxCharacterMessageOpen(true);
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isFinishedClaimingCharacter) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}, [isFinishedClaimingCharacter]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={open}
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "12px",
|
||||
lineHeight: "15px",
|
||||
width: "fit-content",
|
||||
padding: "12px 32px",
|
||||
backgroundColor: "#C50009",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "none",
|
||||
"&:hover": { backgroundColor: "#A7080B", boxShadow: "none" },
|
||||
}}
|
||||
>
|
||||
<VideocamIcon sx={{ m: "-5px 0" }} />
|
||||
Watch Tour
|
||||
</Button>
|
||||
<Dialog
|
||||
onClose={close}
|
||||
open={isOpen}
|
||||
fullWidth={true}
|
||||
maxWidth={"sm"}
|
||||
sx={{
|
||||
" .MuiPaper-root": {
|
||||
backgroundColor: isDarkMode ? "#374045" : "white",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle
|
||||
sx={{
|
||||
m: 0,
|
||||
p: "24px 32px 8px",
|
||||
fontSize: "18px",
|
||||
fontWeight: 700,
|
||||
color: isDarkMode ? "white" : "#12181C",
|
||||
}}
|
||||
>
|
||||
D&D Beyond Character Sheet Tour
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={close}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: isDarkMode
|
||||
? "rbga(255, 255, 255, 0.54)"
|
||||
: "rgba(0, 0, 0, 0.54)",
|
||||
"&:hover": "#ECEDEE",
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ m: 0, p: "0 32px 24px" }}>
|
||||
<Box
|
||||
noValidate
|
||||
component="form"
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: 0,
|
||||
paddingBottom: "56.25%",
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/ChYPIdCrBdE"
|
||||
title="D&D Beyond Sheet Tour"
|
||||
style={{
|
||||
border: 0,
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "gray",
|
||||
}}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
></iframe>
|
||||
</div>
|
||||
<DialogContentText
|
||||
sx={{
|
||||
color: isDarkMode ? "white" : "#12181C",
|
||||
" a": {
|
||||
color: "#3298ED",
|
||||
textDecoration: "none",
|
||||
},
|
||||
" a:hover": { color: "#53ADF0" },
|
||||
}}
|
||||
>
|
||||
For a complete guide to getting your game started, see{" "}
|
||||
<a href="https://www.dndbeyond.com/posts/754-how-to-play-dungeons-dragons-using-d-d-beyond">
|
||||
How to Play Dungeons & Dragons Using D&D Beyond
|
||||
</a>
|
||||
.
|
||||
</DialogContentText>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={isClaimingCharacter}
|
||||
{...(!signedIn && { href: signupLink })}
|
||||
{...(signedIn && { onClick: handleClaim })}
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "12px",
|
||||
lineHeight: "15px",
|
||||
width: "fit-content",
|
||||
padding: "12px 32px",
|
||||
m: "auto",
|
||||
backgroundColor: "#3298ED",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "none",
|
||||
"&:hover": { backgroundColor: "#53ADF0", boxShadow: "none" },
|
||||
}}
|
||||
>
|
||||
Claim Character
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<MaxCharactersDialog
|
||||
open={isMaxCharacterMessageOpen}
|
||||
onClose={() => setIsMaxCharacterMessageOpen(false)}
|
||||
useMyCharactersLink
|
||||
/>
|
||||
{isFinishedClaimingCharacter && !!newCharacterId && (
|
||||
<ClaimConfirmationDialog
|
||||
characterId={newCharacterId}
|
||||
campaignId={campaignId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WatchTourDialog;
|
||||
@@ -1,4 +0,0 @@
|
||||
import WatchTourDialog from "./WatchTourDialog";
|
||||
|
||||
export default WatchTourDialog;
|
||||
export { WatchTourDialog };
|
||||
Reference in New Issue
Block a user