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 };
|
||||
@@ -1,6 +1,6 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React, { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
AbilityLookup,
|
||||
@@ -38,12 +38,13 @@ import {
|
||||
SpellUtils,
|
||||
WeaponSpellDamageGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { Link } from "~/components/Link";
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import {
|
||||
PaneComponentEnum,
|
||||
PaneIdentifiers,
|
||||
@@ -79,10 +80,12 @@ interface ActionListConfig {
|
||||
theme: CharacterTheme;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
rollContext: IRollContext;
|
||||
rollContext: RollContext;
|
||||
}
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
showNotes: boolean;
|
||||
|
||||
activatables: Array<Activatable>;
|
||||
@@ -100,7 +103,7 @@ interface Props extends DispatchProp {
|
||||
theme: CharacterTheme;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
characterRollContext: IRollContext;
|
||||
characterRollContext: RollContext;
|
||||
inventoryManager: InventoryManager;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
}
|
||||
@@ -429,7 +432,7 @@ class Actions extends React.PureComponent<Props, State> {
|
||||
const { attacksPerActionInfo, theme } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-actions__attacks-heading">
|
||||
Actions •{" "}
|
||||
<span
|
||||
@@ -447,7 +450,7 @@ class Actions extends React.PureComponent<Props, State> {
|
||||
{attacksPerActionInfo.restriction}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -546,7 +549,7 @@ class Actions extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<section className="ct-actions">
|
||||
<h2 style={visuallyHidden}>Actions</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Actions</h2>
|
||||
<TabFilter
|
||||
filters={[
|
||||
...(!hasAttackGroups
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
rulesEngineSelectors,
|
||||
Item,
|
||||
ItemUtils,
|
||||
CampaignUtils,
|
||||
CharacterTheme,
|
||||
InventoryManager,
|
||||
serviceDataSelectors,
|
||||
@@ -93,33 +92,30 @@ class Attunement extends React.PureComponent<Props> {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{partyInfo &&
|
||||
CampaignUtils.isSharingStateActive(
|
||||
CampaignUtils.getSharingState(partyInfo)
|
||||
) && (
|
||||
<>
|
||||
<div className="ct-attunement__group-header">
|
||||
Party Items Requiring Attunement
|
||||
</div>
|
||||
<div className="ct-attunement__group-items">
|
||||
{sortedItems.party?.length > 0 ? (
|
||||
sortedItems.party?.map((item) => (
|
||||
<AttunementItem
|
||||
key={ItemUtils.getUniqueKey(item)}
|
||||
item={item}
|
||||
onItemShow={this.handleItemShow}
|
||||
theme={theme}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="ct-attunement__group-empty">
|
||||
Party items that you can attune to will display here as you
|
||||
make them active.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{partyInfo && (
|
||||
<>
|
||||
<div className="ct-attunement__group-header">
|
||||
Party Items Requiring Attunement
|
||||
</div>
|
||||
<div className="ct-attunement__group-items">
|
||||
{sortedItems.party?.length > 0 ? (
|
||||
sortedItems.party?.map((item) => (
|
||||
<AttunementItem
|
||||
key={ItemUtils.getUniqueKey(item)}
|
||||
item={item}
|
||||
onItemShow={this.handleItemShow}
|
||||
theme={theme}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="ct-attunement__group-empty">
|
||||
Party items that you can attune to will display here as you
|
||||
make them active.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,49 +1,37 @@
|
||||
import jss, { StyleSheet } from "jss";
|
||||
import preset from "jss-preset-default";
|
||||
import React from "react";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
|
||||
import { BackdropInfo } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { DDB_MEDIA_URL } from "../../../../../constants";
|
||||
|
||||
interface Props {
|
||||
interface BackdropStylesProps {
|
||||
backdrop: BackdropInfo;
|
||||
}
|
||||
export default class BackdropStyles extends React.PureComponent<Props> {
|
||||
sheet: StyleSheet | null = null;
|
||||
|
||||
componentDidMount() {
|
||||
jss.setup(preset());
|
||||
this.renderStyleSheet(this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeStyleSheet();
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<Props>,
|
||||
prevState: Readonly<{}>,
|
||||
snapshot?: any
|
||||
): void {
|
||||
this.removeStyleSheet();
|
||||
this.renderStyleSheet(this.props);
|
||||
}
|
||||
|
||||
renderStyleSheet = (props: Props): void => {
|
||||
const { backdrop } = props;
|
||||
export const BackdropStyles: FC<BackdropStylesProps> = ({ backdrop }) => {
|
||||
const [sheet, setSheet] = useState<StyleSheet | null>(null);
|
||||
|
||||
const renderStyleSheet = (): void => {
|
||||
if (backdrop.backdropAvatarUrl !== null) {
|
||||
this.sheet = jss.createStyleSheet({});
|
||||
let tempSheet = jss.createStyleSheet({});
|
||||
|
||||
if (this.sheet !== null) {
|
||||
const bodyStyles = window.getComputedStyle(document.body);
|
||||
const newNavHeight = bodyStyles.getPropertyValue(
|
||||
"--top-navigation-height"
|
||||
);
|
||||
const navHeightVar = newNavHeight
|
||||
? "--top-navigation-height"
|
||||
: "--ttui-site-nav-height";
|
||||
|
||||
if (tempSheet !== null) {
|
||||
let breakpointRules: Record<string, string> = [
|
||||
{ width: 768, height: 152, image: backdrop.backdropAvatarUrl },
|
||||
{ width: 1024, height: 218, image: backdrop.backdropAvatarUrl },
|
||||
{ width: 1200, height: 230, image: backdrop.backdropAvatarUrl },
|
||||
{ width: 768, image: backdrop.backdropAvatarUrl },
|
||||
{ width: 1024, image: backdrop.backdropAvatarUrl },
|
||||
{ width: 1200, image: backdrop.backdropAvatarUrl },
|
||||
{
|
||||
width: 1921,
|
||||
height: 230,
|
||||
image:
|
||||
backdrop.smallBackdropAvatarUrl === null
|
||||
? ""
|
||||
@@ -51,7 +39,6 @@ export default class BackdropStyles extends React.PureComponent<Props> {
|
||||
},
|
||||
{
|
||||
width: 2561,
|
||||
height: 230,
|
||||
image:
|
||||
backdrop.largeBackdropAvatarUrl === null
|
||||
? ""
|
||||
@@ -59,30 +46,42 @@ export default class BackdropStyles extends React.PureComponent<Props> {
|
||||
},
|
||||
].reduce((acc, breakpoint) => {
|
||||
acc[`@media (min-width: ${breakpoint.width}px)`] = {
|
||||
background: `url(${breakpoint.image}) no-repeat center ${breakpoint.height}px, url(${DDB_MEDIA_URL}/attachments/0/84/background_texture.png) #f9f9f9 !important`,
|
||||
background: `url(${breakpoint.image}) no-repeat center calc(var(${navHeightVar}) + var(--sheet-header-height)), url(${DDB_MEDIA_URL}/attachments/0/84/background_texture.png) #f9f9f9 !important`,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.sheet.addRules({
|
||||
tempSheet.addRules({
|
||||
"@global": {
|
||||
"html body.body-rpgcharacter-sheet": breakpointRules,
|
||||
},
|
||||
});
|
||||
|
||||
this.sheet.attach();
|
||||
tempSheet.attach();
|
||||
setSheet(tempSheet);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
removeStyleSheet = (): void => {
|
||||
if (this.sheet) {
|
||||
jss.removeStyleSheet(this.sheet);
|
||||
this.sheet = null;
|
||||
const removeStyleSheet = (): void => {
|
||||
if (sheet) {
|
||||
jss.removeStyleSheet(sheet);
|
||||
setSheet(null);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
jss.setup(preset());
|
||||
renderStyleSheet();
|
||||
return () => {
|
||||
removeStyleSheet();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
removeStyleSheet();
|
||||
renderStyleSheet();
|
||||
}, [backdrop]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import BackdropStyles from "./BackdropStyles";
|
||||
|
||||
export default BackdropStyles;
|
||||
export { BackdropStyles };
|
||||
+66
-77
@@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { FC } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Link as RouterLink, useSearchParams } from "react-router-dom";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import {
|
||||
@@ -25,15 +27,17 @@ import {
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { WatchTourDialog } from "~/subApps/sheet/components/WatchTourDialog/WatchTourDialog";
|
||||
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import { GameLogState } from "../../../Shared/stores/typings";
|
||||
import WatchTourDialog from "../../components/WatchTourDialog";
|
||||
import { sheetAppSelectors } from "../../selectors";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import CharacterHeaderInfo from "../CharacterHeaderInfo";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
campaign: CampaignDataContract | null;
|
||||
builderUrl: string;
|
||||
items: Array<Item>;
|
||||
@@ -45,88 +49,69 @@ interface Props extends DispatchProp {
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
}
|
||||
|
||||
class CharacterHeaderDesktop extends React.PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleCampaignShow = (): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
const CharacterHeaderDesktop: FC<Props> = ({
|
||||
paneHistoryStart,
|
||||
items,
|
||||
builderUrl,
|
||||
campaign,
|
||||
preferences,
|
||||
isReadonly,
|
||||
theme,
|
||||
gameLog,
|
||||
status,
|
||||
}) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
const handleCampaignShow = (): void => {
|
||||
paneHistoryStart(PaneComponentEnum.CAMPAIGN);
|
||||
};
|
||||
|
||||
handleShortResetClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleShortResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.SHORT_REST);
|
||||
};
|
||||
|
||||
handleLongResetClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleLongResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.LONG_REST);
|
||||
};
|
||||
|
||||
handleShareClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleShareClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.SHARE_URL);
|
||||
};
|
||||
|
||||
hasMagicItem = (): boolean => {
|
||||
const { items } = this.props;
|
||||
|
||||
const hasMagicItem = (): boolean => {
|
||||
return !!items.find((item) => ItemUtils.isMagic(item));
|
||||
};
|
||||
|
||||
renderSideContent = (): React.ReactNode => {
|
||||
const {
|
||||
builderUrl,
|
||||
campaign,
|
||||
preferences,
|
||||
isReadonly,
|
||||
theme,
|
||||
gameLog,
|
||||
status,
|
||||
} = this.props;
|
||||
|
||||
const renderSideContent = (): React.ReactNode => {
|
||||
if (isReadonly) {
|
||||
if (status === CharacterStatusSlug.PREMADE) {
|
||||
return <WatchTourDialog />;
|
||||
}
|
||||
|
||||
if (!campaign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CampaignSummary
|
||||
campaign={campaign}
|
||||
onCampaignShow={this.handleCampaignShow}
|
||||
onCampaignShow={handleCampaignShow}
|
||||
gameLog={gameLog}
|
||||
theme={theme}
|
||||
isDesktop
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{preferences !== null &&
|
||||
preferences.privacyType ===
|
||||
Constants.PreferencePrivacyTypeEnum.PUBLIC && (
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--share">
|
||||
<div
|
||||
className="ct-character-header-desktop__button"
|
||||
onClick={this.handleShareClick}
|
||||
onClick={handleShareClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -142,7 +127,7 @@ class CharacterHeaderDesktop extends React.PureComponent<Props> {
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--short-rest">
|
||||
<div
|
||||
className="ct-character-header-desktop__button"
|
||||
onClick={this.handleShortResetClick}
|
||||
onClick={handleShortResetClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -156,8 +141,11 @@ class CharacterHeaderDesktop extends React.PureComponent<Props> {
|
||||
</div>
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--long-rest">
|
||||
<div
|
||||
className="ct-character-header-desktop__button"
|
||||
onClick={this.handleLongResetClick}
|
||||
className={clsx([
|
||||
"ct-character-header-desktop__button",
|
||||
isVttView && "ct-character-header-desktop__button--vttView",
|
||||
])}
|
||||
onClick={handleLongResetClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -177,45 +165,46 @@ class CharacterHeaderDesktop extends React.PureComponent<Props> {
|
||||
{/*</div>*/}
|
||||
{/*</div>*/}
|
||||
{/*}*/}
|
||||
{campaign !== null && (
|
||||
{!isVttView && (
|
||||
<CampaignSummary
|
||||
campaign={campaign}
|
||||
onCampaignShow={this.handleCampaignShow}
|
||||
onCampaignShow={handleCampaignShow}
|
||||
gameLog={gameLog}
|
||||
theme={theme}
|
||||
isDesktop
|
||||
/>
|
||||
)}
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--builder">
|
||||
<Tooltip
|
||||
isDarkMode={theme.isDarkMode}
|
||||
title="Go to builder"
|
||||
className="ct-character-header-desktop__builder"
|
||||
>
|
||||
<RouterLink
|
||||
to={builderUrl}
|
||||
className="ct-character-header-desktop__builder-link"
|
||||
aria-label="Go to builder"
|
||||
{!isVttView && (
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--builder">
|
||||
<Tooltip
|
||||
isDarkMode={theme.isDarkMode}
|
||||
title="Go to builder"
|
||||
className="ct-character-header-desktop__builder"
|
||||
>
|
||||
<LightBuilderSvg />
|
||||
</RouterLink>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
<RouterLink
|
||||
to={builderUrl}
|
||||
className="ct-character-header-desktop__builder-link"
|
||||
aria-label="Go to builder"
|
||||
>
|
||||
<LightBuilderSvg />
|
||||
</RouterLink>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-character-header-desktop">
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group-tidbits">
|
||||
<CharacterHeaderInfo />
|
||||
</div>
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--gap" />
|
||||
{this.renderSideContent()}
|
||||
return (
|
||||
<div className="ct-character-header-desktop">
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group-tidbits">
|
||||
<CharacterHeaderInfo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<div className="ct-character-header-desktop__group ct-character-header-desktop__group--gap" />
|
||||
{renderSideContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: SheetAppState) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import CharacterHeaderDesktop from "./CharacterHeaderDesktop";
|
||||
|
||||
export default CharacterHeaderDesktop;
|
||||
export { CharacterHeaderDesktop };
|
||||
+8
-3
@@ -31,6 +31,7 @@ interface Props {
|
||||
preferences: CharacterPreferences;
|
||||
decorationInfo: DecorationInfo;
|
||||
isReadonly: boolean;
|
||||
isVttView?: boolean;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
}
|
||||
class CharacterHeaderInfo extends React.PureComponent<Props, {}> {
|
||||
@@ -54,6 +55,7 @@ class CharacterHeaderInfo extends React.PureComponent<Props, {}> {
|
||||
ruleData,
|
||||
decorationInfo,
|
||||
isReadonly,
|
||||
isVttView,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -73,10 +75,13 @@ class CharacterHeaderInfo extends React.PureComponent<Props, {}> {
|
||||
ruleData={ruleData}
|
||||
xpInfo={xpInfo}
|
||||
isInteractive={!isReadonly}
|
||||
isVttView={isVttView}
|
||||
calloutNode={
|
||||
<ThemeButton size="small" style="outline">
|
||||
Manage
|
||||
</ThemeButton>
|
||||
!isVttView && (
|
||||
<ThemeButton size="small" style="outline">
|
||||
Manage
|
||||
</ThemeButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+102
-51
@@ -1,5 +1,8 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { FC } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
@@ -9,19 +12,27 @@ import {
|
||||
Constants,
|
||||
HitPointInfo,
|
||||
RuleData,
|
||||
CharacterTheme,
|
||||
CampaignDataContract,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import ListTimeline from "@dndbeyond/fontawesome-cache/svgs/light/list-timeline.svg";
|
||||
import { GameLogNotificationWrapper } from "@dndbeyond/game-log-components";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { WatchTourDialog } from "~/subApps/sheet/components/WatchTourDialog";
|
||||
import { GameLogState } from "~/tools/js/Shared/stores/typings";
|
||||
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import StatusSummaryMobile from "../../components/StatusSummaryMobile";
|
||||
import WatchTourDialog from "../../components/WatchTourDialog";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import CharacterHeaderInfo from "../CharacterHeaderInfo";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
hitPointInfo: HitPointInfo;
|
||||
fails: number;
|
||||
successes: number;
|
||||
@@ -31,63 +42,100 @@ interface Props extends DispatchProp {
|
||||
isReadonly: boolean;
|
||||
status: CharacterStatusSlug | null;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
gameLog?: GameLogState;
|
||||
theme: CharacterTheme;
|
||||
campaign: CampaignDataContract | null;
|
||||
}
|
||||
class CharacterHeaderMobile extends React.PureComponent<Props> {
|
||||
handleHealthSummaryClick = (): void => {
|
||||
const { paneHistoryStart, isReadonly } = this.props;
|
||||
|
||||
const CharacterHeaderMobile: FC<Props> = ({
|
||||
paneHistoryStart,
|
||||
dispatch,
|
||||
hitPointInfo,
|
||||
fails,
|
||||
successes,
|
||||
deathCause,
|
||||
inspiration,
|
||||
ruleData,
|
||||
isReadonly,
|
||||
status,
|
||||
gameLog,
|
||||
theme,
|
||||
campaign,
|
||||
}) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
const handleHealthSummaryClick = (): void => {
|
||||
if (!isReadonly) {
|
||||
paneHistoryStart(PaneComponentEnum.HEALTH_MANAGE);
|
||||
}
|
||||
};
|
||||
|
||||
handleInspirationClick = (): void => {
|
||||
const { inspiration, dispatch } = this.props;
|
||||
|
||||
const handleInspirationClick = (): void => {
|
||||
dispatch(characterActions.inspirationSet(!inspiration));
|
||||
};
|
||||
const handleGameLogClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
// Assuming dispatch is available in props or context
|
||||
paneHistoryStart(PaneComponentEnum.GAME_LOG);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
hitPointInfo,
|
||||
fails,
|
||||
successes,
|
||||
deathCause,
|
||||
inspiration,
|
||||
ruleData,
|
||||
isReadonly,
|
||||
status,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-character-header-mobile">
|
||||
<div className="ct-character-header-mobile__group ct-character-header-mobile__group-tidbits">
|
||||
<CharacterHeaderInfo />
|
||||
</div>
|
||||
<div className="ct-character-header-mobile__group ct-character-header-mobile__group--gap" />
|
||||
{isReadonly && status === CharacterStatusSlug.PREMADE ? (
|
||||
<div>
|
||||
<WatchTourDialog />
|
||||
</div>
|
||||
) : (
|
||||
<div className="ct-character-header-mobile__group ct-character-header-mobile__group--summary">
|
||||
<StatusSummaryMobile
|
||||
hitPointInfo={hitPointInfo}
|
||||
fails={fails}
|
||||
successes={successes}
|
||||
deathCause={deathCause}
|
||||
inspiration={inspiration}
|
||||
ruleData={ruleData}
|
||||
onHealthClick={this.handleHealthSummaryClick}
|
||||
onInspirationClick={this.handleInspirationClick}
|
||||
isInteractive={!isReadonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div
|
||||
className={clsx([
|
||||
"ct-character-header-mobile",
|
||||
isVttView && "ct-character-header-mobile--vttView",
|
||||
])}
|
||||
>
|
||||
<div
|
||||
className={clsx([
|
||||
"ct-character-header-mobile__group",
|
||||
"ct-character-header-mobile__group-tidbits",
|
||||
isVttView && "ct-character-header-mobile__group-tidbits--vttView",
|
||||
])}
|
||||
>
|
||||
<CharacterHeaderInfo isVttView={isVttView} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
{!isVttView && !isReadonly && (
|
||||
<div className={styles.campaignButtonContainer}>
|
||||
<GameLogNotificationWrapper
|
||||
themeColor={theme.themeColor}
|
||||
gameLogIsOpen={gameLog?.isOpen ?? false}
|
||||
notificationOnClick={handleGameLogClick}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
aria-roledescription="Game Log"
|
||||
className={clsx(styles.campaignButtonGroup)}
|
||||
onClick={handleGameLogClick}
|
||||
>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<ListTimeline className={clsx(styles.campaignButtonIcon)} />
|
||||
</div>
|
||||
</div>
|
||||
</GameLogNotificationWrapper>
|
||||
</div>
|
||||
)}
|
||||
{isReadonly && status === CharacterStatusSlug.PREMADE ? (
|
||||
<div>
|
||||
<WatchTourDialog />
|
||||
</div>
|
||||
) : (
|
||||
<div className="ct-character-header-mobile__group ct-character-header-mobile__group--summary">
|
||||
<StatusSummaryMobile
|
||||
hitPointInfo={hitPointInfo}
|
||||
fails={fails}
|
||||
successes={successes}
|
||||
deathCause={deathCause}
|
||||
inspiration={inspiration}
|
||||
ruleData={ruleData}
|
||||
onHealthClick={handleHealthSummaryClick}
|
||||
onInspirationClick={handleInspirationClick}
|
||||
isInteractive={!isReadonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: SheetAppState) {
|
||||
return {
|
||||
@@ -99,6 +147,9 @@ function mapStateToProps(state: SheetAppState) {
|
||||
deathCause: rulesEngineSelectors.getDeathCause(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
status: characterSelectors.getStatusSlug(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
gameLog: appEnvSelectors.getGameLog(state),
|
||||
campaign: rulesEngineSelectors.getCampaign(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+90
-74
@@ -1,5 +1,8 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { FC } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
||||
import {
|
||||
@@ -22,20 +25,26 @@ import {
|
||||
RuleData,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
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 { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { WatchTourDialog } from "~/subApps/sheet/components/WatchTourDialog";
|
||||
import { GameLogState } from "~/tools/js/Shared/stores/typings";
|
||||
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import StatusSummaryMobile from "../../components/StatusSummaryMobile";
|
||||
import WatchTourDialog from "../../components/WatchTourDialog";
|
||||
import { sheetAppSelectors } from "../../selectors";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import { CharacterHeaderInfo } from "../CharacterHeaderInfo";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
hitPointInfo: HitPointInfo;
|
||||
fails: number;
|
||||
successes: number;
|
||||
@@ -50,84 +59,67 @@ interface Props extends DispatchProp {
|
||||
theme: CharacterTheme;
|
||||
status: CharacterStatusSlug | null;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
gameLog?: GameLogState;
|
||||
}
|
||||
class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
handleCampaignShow = (): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
const CharacterHeaderTablet: FC<Props> = ({
|
||||
paneHistoryStart,
|
||||
isReadonly,
|
||||
dispatch,
|
||||
inspiration,
|
||||
items,
|
||||
hitPointInfo,
|
||||
fails,
|
||||
successes,
|
||||
deathCause,
|
||||
builderUrl,
|
||||
preferences,
|
||||
ruleData,
|
||||
theme,
|
||||
status,
|
||||
gameLog,
|
||||
campaign,
|
||||
}) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
const handleCampaignShow = (): void => {
|
||||
paneHistoryStart(PaneComponentEnum.CAMPAIGN);
|
||||
};
|
||||
|
||||
handleShortResetClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleShortResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.SHORT_REST);
|
||||
};
|
||||
|
||||
handleGameLogClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleGameLogClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.GAME_LOG);
|
||||
};
|
||||
|
||||
handleLongResetClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleLongResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.LONG_REST);
|
||||
};
|
||||
|
||||
handleShareClick = (evt: React.MouseEvent): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
const handleShareClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.SHARE_URL);
|
||||
};
|
||||
|
||||
handleHealthSummaryClick = (): void => {
|
||||
const { paneHistoryStart, isReadonly } = this.props;
|
||||
|
||||
const handleHealthSummaryClick = (): void => {
|
||||
if (!isReadonly) {
|
||||
paneHistoryStart(PaneComponentEnum.HEALTH_MANAGE);
|
||||
}
|
||||
};
|
||||
|
||||
handleInspirationClick = (): void => {
|
||||
const { inspiration, dispatch } = this.props;
|
||||
|
||||
const handleInspirationClick = (): void => {
|
||||
dispatch(characterActions.inspirationSet(!inspiration));
|
||||
};
|
||||
|
||||
hasMagicItem = (): boolean => {
|
||||
const { items } = this.props;
|
||||
|
||||
const hasMagicItem = (): boolean => {
|
||||
return !!items.find((item) => ItemUtils.isMagic(item));
|
||||
};
|
||||
|
||||
renderSideContent = (): React.ReactNode => {
|
||||
const {
|
||||
hitPointInfo,
|
||||
fails,
|
||||
successes,
|
||||
deathCause,
|
||||
inspiration,
|
||||
builderUrl,
|
||||
preferences,
|
||||
isReadonly,
|
||||
ruleData,
|
||||
theme,
|
||||
status,
|
||||
} = this.props;
|
||||
|
||||
const renderSideContent = (): React.ReactNode => {
|
||||
if (isReadonly) {
|
||||
if (status === CharacterStatusSlug.PREMADE) {
|
||||
return (
|
||||
@@ -137,7 +129,7 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--summary">
|
||||
<StatusSummaryMobile
|
||||
hitPointInfo={hitPointInfo}
|
||||
@@ -146,24 +138,24 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
inspiration={inspiration}
|
||||
ruleData={ruleData}
|
||||
deathCause={deathCause}
|
||||
onHealthClick={this.handleHealthSummaryClick}
|
||||
onInspirationClick={this.handleInspirationClick}
|
||||
onHealthClick={handleHealthSummaryClick}
|
||||
onInspirationClick={handleInspirationClick}
|
||||
isInteractive={!isReadonly}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{preferences !== null &&
|
||||
preferences.privacyType ===
|
||||
Constants.PreferencePrivacyTypeEnum.PUBLIC && (
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--share">
|
||||
<div
|
||||
className="ct-character-header-tablet__button"
|
||||
onClick={this.handleShareClick}
|
||||
onClick={handleShareClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -176,10 +168,16 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--short-rest">
|
||||
<div
|
||||
className={clsx([
|
||||
"ct-character-header-tablet__group",
|
||||
"ct-character-header-tablet__group--short-rest",
|
||||
styles.shortRest,
|
||||
])}
|
||||
>
|
||||
<div
|
||||
className="ct-character-header-tablet__button"
|
||||
onClick={this.handleShortResetClick}
|
||||
onClick={handleShortResetClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -194,7 +192,7 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--long-rest">
|
||||
<div
|
||||
className="ct-character-header-tablet__button"
|
||||
onClick={this.handleLongResetClick}
|
||||
onClick={handleLongResetClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
@@ -206,6 +204,26 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!isVttView && (
|
||||
<div className={styles.campaignButtonContainer}>
|
||||
<GameLogNotificationWrapper
|
||||
themeColor={theme.themeColor}
|
||||
gameLogIsOpen={gameLog?.isOpen ?? false}
|
||||
notificationOnClick={handleGameLogClick}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
aria-roledescription="Game Log"
|
||||
className={clsx(styles.campaignButtonGroup)}
|
||||
onClick={handleGameLogClick}
|
||||
>
|
||||
<div className={clsx(styles.campaignButton)}>
|
||||
<ListTimeline className={clsx(styles.campaignButtonIcon)} />
|
||||
</div>
|
||||
</div>
|
||||
</GameLogNotificationWrapper>
|
||||
</div>
|
||||
)}
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--builder">
|
||||
<Tooltip
|
||||
isDarkMode={theme.isDarkMode}
|
||||
@@ -215,7 +233,7 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
<Link
|
||||
href={builderUrl}
|
||||
className="ct-character-header-tablet__builder-link"
|
||||
useRouter
|
||||
userouter
|
||||
>
|
||||
<LightBuilderSvg />
|
||||
</Link>
|
||||
@@ -229,27 +247,24 @@ class CharacterHeaderTablet extends React.PureComponent<Props> {
|
||||
deathCause={deathCause}
|
||||
inspiration={inspiration}
|
||||
ruleData={ruleData}
|
||||
onHealthClick={this.handleHealthSummaryClick}
|
||||
onInspirationClick={this.handleInspirationClick}
|
||||
onHealthClick={handleHealthSummaryClick}
|
||||
onInspirationClick={handleInspirationClick}
|
||||
isInteractive={!isReadonly}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-character-header-tablet">
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group-tidbits">
|
||||
<CharacterHeaderInfo />
|
||||
</div>
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group--gap" />
|
||||
{this.renderSideContent()}
|
||||
return (
|
||||
<div className="ct-character-header-tablet">
|
||||
<div className="ct-character-header-tablet__group ct-character-header-tablet__group-tidbits">
|
||||
<CharacterHeaderInfo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
{renderSideContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: SheetAppState) {
|
||||
return {
|
||||
@@ -266,6 +281,7 @@ function mapStateToProps(state: SheetAppState) {
|
||||
deathCause: rulesEngineSelectors.getDeathCause(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
status: characterSelectors.getStatusSlug(state),
|
||||
gameLog: appEnvSelectors.getGameLog(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import clsx from "clsx";
|
||||
import { throttle } from "lodash";
|
||||
import { createRef, PureComponent } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
characterEnvSelectors,
|
||||
@@ -11,13 +14,15 @@ import {
|
||||
CharacterPreferences,
|
||||
characterSelectors,
|
||||
CharacterStatusSlug,
|
||||
CampaignDataContract,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { GameLogContextProvider } from "@dndbeyond/game-log-components";
|
||||
|
||||
import { Link } from "~/components/Link";
|
||||
import { NotificationSystem } from "~/components/NotificationSystem";
|
||||
import { PremadeCharacterEditStatus } from "~/components/PremadeCharacterEditStatus";
|
||||
import { usePositioning } from "~/hooks/usePositioning";
|
||||
import { CharacterSheetGuidedTour } from "~/subApps/sheet/components/CharacterSheetGuidedTour";
|
||||
import { ClaimPremadeButton } from "~/subApps/sheet/components/ClaimPremadeButton";
|
||||
import { MobileNav } from "~/subApps/sheet/components/MobileNav";
|
||||
|
||||
import { useHeadContext } from "../../../../../contexts/Head";
|
||||
@@ -43,7 +48,6 @@ import {
|
||||
} from "../../../Shared/stores/typings";
|
||||
import config from "../../../config";
|
||||
import { sheetActions } from "../../actions";
|
||||
import ClaimPremadeButton from "../../components/ClaimPremadeButton";
|
||||
import { InvalidCharacter } from "../../components/InvalidCharacter";
|
||||
import {
|
||||
DESKTOP_COMPONENT_START_WIDTH,
|
||||
@@ -53,11 +57,13 @@ import {
|
||||
import { sheetAppSelectors, sheetSelectors } from "../../selectors";
|
||||
import { SheetAppState, SheetPositioningInfo } from "../../typings";
|
||||
import CharacterSheetDesktop from "../CharacterSheetDesktop";
|
||||
import CharacterSheetGuidedTour from "../CharacterSheetGuidedTour";
|
||||
import CharacterSheetMobile from "../CharacterSheetMobile";
|
||||
import CharacterSheetTablet from "../CharacterSheetTablet";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
sheetPosition: SheetPositioningInfo;
|
||||
initFailed: boolean;
|
||||
loadingStatus: Constants.CharacterLoadingStatusEnum;
|
||||
@@ -81,8 +87,11 @@ interface Props extends DispatchProp {
|
||||
preferences: CharacterPreferences;
|
||||
setManager: (manager: any) => void;
|
||||
setTitle: (title: string) => void;
|
||||
isVttView: boolean;
|
||||
characterStatus: CharacterStatusSlug | null;
|
||||
userRoles: UserRoles;
|
||||
userId: number;
|
||||
campaign: CampaignDataContract | null;
|
||||
}
|
||||
interface State {
|
||||
curseHeaderHeight: number;
|
||||
@@ -146,6 +155,8 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
characterStatus,
|
||||
username,
|
||||
userRoles,
|
||||
userId,
|
||||
campaign,
|
||||
} = this.props;
|
||||
|
||||
if (loadingStatus === Constants.CharacterLoadingStatusEnum.LOADED) {
|
||||
@@ -163,6 +174,7 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
appEnvActions.dataSet({
|
||||
isReadonly: !canEdit,
|
||||
diceEnabled: canEdit ? diceEnabled : false,
|
||||
isUserDM: Number(userId) === campaign?.dmUserId,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -316,7 +328,7 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
|
||||
let children: React.ReactNode;
|
||||
if (Component) {
|
||||
let childrenProps = componentProps ? componentProps : {};
|
||||
let childrenProps = componentProps || {};
|
||||
children = <Component {...childrenProps} />;
|
||||
}
|
||||
|
||||
@@ -449,15 +461,12 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
case AppErrorTypeEnum.ACCESS_DENIED:
|
||||
contentNode = (
|
||||
<>
|
||||
<p>You don't have access to this page.</p>
|
||||
<p>
|
||||
If you are trying to access a character sheet, make sure its
|
||||
privacy setting is configured correctly for you to access it.
|
||||
</p>
|
||||
<p>
|
||||
It is also possible that you are trying to enter the 403rd level
|
||||
of the endless dungeon and the ancient dragon Rylzrayrth is rising
|
||||
up to block your path...
|
||||
<p style={{ margin: 0 }}>You reach for the sheet,</p>
|
||||
<p style={{ margin: 0 }}>But the sigil reads "Private"—</p>
|
||||
<p style={{ margin: 0 }}>Permission denied.</p>
|
||||
<p style={{ marginTop: 15 }}>
|
||||
Ask the owner to set their Character Sheet to Campaign Only or
|
||||
Public in their character's settings.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
@@ -560,6 +569,7 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
theme,
|
||||
isReadonly,
|
||||
characterStatus,
|
||||
isVttView,
|
||||
} = this.props;
|
||||
|
||||
let sheetStyles: React.CSSProperties = {};
|
||||
@@ -591,46 +601,43 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<CharacterSheetGuidedTour>
|
||||
<GameLogContextProvider
|
||||
baseUrl={gameLog.apiEndpoint}
|
||||
ddbApiUrl={gameLog.ddbApiEndpoint}
|
||||
diceServiceUrl={diceFeatureConfiguration.apiEndpoint}
|
||||
authUrl={authEndpoint}
|
||||
diceThumbnailsUrl={`${diceFeatureConfiguration.assetBaseLocation}/images/thumbnails`}
|
||||
entityId={characterId ? characterId.toString() : ""}
|
||||
>
|
||||
<div className={classNames.join(" ")}>
|
||||
{this.renderSyncBlocker()}
|
||||
<div
|
||||
className="ct-character-sheet__inner"
|
||||
ref={this.sheetRef}
|
||||
style={sheetStyles}
|
||||
<div className={classNames.join(" ")}>
|
||||
{this.renderSyncBlocker()}
|
||||
<div
|
||||
className={clsx([
|
||||
"ct-character-sheet__inner",
|
||||
!everythingIsLoadedWithoutErrors && styles.sheetNotReady,
|
||||
])}
|
||||
ref={this.sheetRef}
|
||||
style={sheetStyles}
|
||||
>
|
||||
<ThemeManagerProvider
|
||||
manager={{
|
||||
lightOrDark: theme.isDarkMode ? "dark" : "light",
|
||||
primary: theme.themeColor,
|
||||
}}
|
||||
>
|
||||
<ThemeManagerProvider
|
||||
manager={{
|
||||
lightOrDark: theme.isDarkMode ? "dark" : "light",
|
||||
primary: theme.themeColor,
|
||||
}}
|
||||
>
|
||||
{everythingIsLoadedWithoutErrors && (
|
||||
<PremadeCharacterEditStatus
|
||||
characterStatus={characterStatus}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
)}
|
||||
{this.renderContent()}
|
||||
{everythingIsLoadedWithoutErrors && <MobileNav />}
|
||||
</ThemeManagerProvider>
|
||||
</div>
|
||||
<NotificationSystem ref={this.notificationSystem} />
|
||||
{diceEnabled && !isReadonly && (
|
||||
<DiceContainer canShow={everythingIsLoadedWithoutErrors} />
|
||||
)}
|
||||
{everythingIsLoadedWithoutErrors &&
|
||||
characterStatus === CharacterStatusSlug.PREMADE &&
|
||||
isReadonly && <ClaimPremadeButton />}
|
||||
{everythingIsLoadedWithoutErrors && (
|
||||
<PremadeCharacterEditStatus
|
||||
characterStatus={characterStatus}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
)}
|
||||
{this.renderContent()}
|
||||
{everythingIsLoadedWithoutErrors && <MobileNav />}
|
||||
</ThemeManagerProvider>
|
||||
</div>
|
||||
</GameLogContextProvider>
|
||||
<NotificationSystem ref={this.notificationSystem} />
|
||||
{diceEnabled && !isReadonly && (
|
||||
<DiceContainer
|
||||
canShow={everythingIsLoadedWithoutErrors}
|
||||
isVttView={isVttView}
|
||||
/>
|
||||
)}
|
||||
{everythingIsLoadedWithoutErrors &&
|
||||
characterStatus === CharacterStatusSlug.PREMADE &&
|
||||
isReadonly && <ClaimPremadeButton />}
|
||||
</div>
|
||||
</CharacterSheetGuidedTour>
|
||||
);
|
||||
}
|
||||
@@ -639,11 +646,14 @@ class CharacterSheet extends PureComponent<Props, State> {
|
||||
const CharacterSheetWrapper = (props) => {
|
||||
const { setTitle } = useHeadContext();
|
||||
const { getSheetPositioning } = usePositioning();
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
return (
|
||||
<CharacterSheet
|
||||
{...props}
|
||||
setTitle={setTitle}
|
||||
sheetPosition={getSheetPositioning()}
|
||||
isVttView={isVttView}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -671,6 +681,8 @@ function mapStateToProps(state: SheetAppState) {
|
||||
characterName: rulesEngineSelectors.getName(state),
|
||||
preferences: rulesEngineSelectors.getPreferences(state),
|
||||
characterStatus: characterSelectors.getStatusSlug(state),
|
||||
userId: appEnvSelectors.getUserId(state),
|
||||
campaign: characterSelectors.getCampaign(state),
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps)(CharacterSheetWrapper);
|
||||
|
||||
+21
-20
@@ -1,6 +1,7 @@
|
||||
import { spring, TransitionMotion } from "@serprex/react-motion";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { ThemeStyles } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -20,7 +21,7 @@ import { AppEnvDimensionsState } from "../../../Shared/stores/typings";
|
||||
import Subsections from "../../components/Subsections";
|
||||
import { DESKTOP_LARGE_COMPONENT_START_WIDTH } from "../../config";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import BackdropStyles from "../BackdropStyles";
|
||||
import { BackdropStyles } from "../BackdropStyles";
|
||||
import { CharacterHeaderDesktop } from "../CharacterHeaderDesktop";
|
||||
import Combat from "../Combat";
|
||||
import PrimaryBox from "../PrimaryBox";
|
||||
@@ -30,7 +31,9 @@ import SavingThrowsDesktop from "../SavingThrowsDesktop";
|
||||
import SensesDesktop from "../SensesDesktop";
|
||||
import SkillsDesktop from "../SkillsDesktop";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
sidebarInfo: SidebarInfo;
|
||||
sidebarPosition: SidebarPositionInfo;
|
||||
decorationInfo: DecorationInfo;
|
||||
@@ -64,7 +67,7 @@ class CharacterSheetDesktop extends React.PureComponent<Props, State> {
|
||||
|
||||
renderContent = (): React.ReactNode => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<CharacterHeaderDesktop />
|
||||
<QuickInfo />
|
||||
<Subsections>
|
||||
@@ -75,7 +78,7 @@ class CharacterSheetDesktop extends React.PureComponent<Props, State> {
|
||||
<Combat />
|
||||
<PrimaryBox />
|
||||
</Subsections>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -100,22 +103,20 @@ class CharacterSheetDesktop extends React.PureComponent<Props, State> {
|
||||
]}
|
||||
>
|
||||
{(interpolatedStyles) => (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{interpolatedStyles.map((config) => (
|
||||
<>
|
||||
<Sidebar
|
||||
key={config.key}
|
||||
style={{
|
||||
...sidebarPosition,
|
||||
transform: `translateX(${config.style.transform}px)`,
|
||||
}}
|
||||
setSwipedAmount={(swipedAmount) =>
|
||||
this.setState({ swipedAmount })
|
||||
}
|
||||
/>
|
||||
</>
|
||||
<Sidebar
|
||||
key={config.key}
|
||||
style={{
|
||||
...sidebarPosition,
|
||||
transform: `translateX(${config.style.transform}px)`,
|
||||
}}
|
||||
setSwipedAmount={(swipedAmount) =>
|
||||
this.setState({ swipedAmount })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
</div>
|
||||
@@ -147,13 +148,13 @@ class CharacterSheetDesktop extends React.PureComponent<Props, State> {
|
||||
const { decorationInfo } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{this.renderSheet()}
|
||||
<BackdropStyles
|
||||
backdrop={DecorationUtils.getBackdropInfo(decorationInfo)}
|
||||
/>
|
||||
<ThemeStyles decorationInfo={decorationInfo} />
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
Constants,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { useFeatureFlags } from "~/contexts/FeatureFlag";
|
||||
|
||||
import { GuidedTour } from "../../../Shared/containers/GuidedTour";
|
||||
import { getCharacterSheetSteps } from "./getCharacterSheetSteps";
|
||||
|
||||
const CharacterSheetGuidedTour = ({ children }) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const { characterSheetTourFlag } = useFeatureFlags();
|
||||
const isTablet = useMediaQuery("(min-width: 768px)");
|
||||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
const ready = useSelector(rulesEngineSelectors.isCharacterSheetReady);
|
||||
// const hasSpells = useSelector(rulesEngineSelectors.hasSpells);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const setCurrentStep = (currentStep) => {
|
||||
switch (currentStep) {
|
||||
case 11: {
|
||||
if (!isTablet && !isDesktop) {
|
||||
const toggle = document.querySelector(
|
||||
"[class^='styles_navToggle']"
|
||||
) as HTMLButtonElement;
|
||||
toggle?.click();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 13: {
|
||||
if (isTablet && !isDesktop) {
|
||||
const toggle = document.querySelector(
|
||||
"[class^='styles_navToggle']"
|
||||
) as HTMLButtonElement;
|
||||
toggle?.click();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setStep(currentStep);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// If character sheet has loaded, enable tour
|
||||
if (ready) setLoading(false);
|
||||
}, [ready]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{characterSheetTourFlag && !loading ? (
|
||||
<GuidedTour
|
||||
steps={getCharacterSheetSteps(false, isTablet, isDesktop)}
|
||||
step={step}
|
||||
setStep={setCurrentStep}
|
||||
showOnFirstLoad={true}
|
||||
cookieName="characterSheetGuidedTour"
|
||||
>
|
||||
{children}
|
||||
</GuidedTour>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CharacterSheetGuidedTour;
|
||||
-457
@@ -1,457 +0,0 @@
|
||||
import { StepType } from "@reactour/tour";
|
||||
|
||||
import { GuidedTourStep } from "~/tools/js/Shared/containers/GuidedTour";
|
||||
|
||||
/**
|
||||
* CHARACTER SHEET TOUR STEPS
|
||||
**/
|
||||
export const getCharacterSheetSteps = (
|
||||
hasSpells: boolean,
|
||||
isTablet: boolean,
|
||||
isDesktop: boolean
|
||||
): StepType[] => {
|
||||
const getSelector = (
|
||||
mobileSelector: string,
|
||||
tabletSelector?: string,
|
||||
desktopSelector?: string
|
||||
) =>
|
||||
isDesktop && desktopSelector
|
||||
? desktopSelector
|
||||
: isTablet && tabletSelector
|
||||
? tabletSelector
|
||||
: mobileSelector;
|
||||
|
||||
const stepList = [
|
||||
{
|
||||
selector: getSelector("html"),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Welcome!"
|
||||
content={
|
||||
<>
|
||||
Welcome to your character sheet! Here, you can find information
|
||||
about your character, roll dice, and find more information about
|
||||
other characters in your game. If you ever have any questions
|
||||
about rules or what something is for, you can click on almost
|
||||
anything to learn more!
|
||||
</>
|
||||
}
|
||||
showClose
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(".dice-toolbar"),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Digital Dice"
|
||||
content={
|
||||
<>
|
||||
Click here if you need to make a custom dice roll. Your dice
|
||||
collection can be found{" "}
|
||||
<a
|
||||
href="https://www.dndbeyond.com/my-dice"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-character-header-mobile__group-tidbits",
|
||||
".ct-character-header-tablet__group-tidbits",
|
||||
".ct-character-header-desktop__group-tidbits"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Character Overview"
|
||||
content={
|
||||
<>
|
||||
Basic information about your character such as Name, Species,
|
||||
Class, and Level can be found here. Click this area to change your
|
||||
character's name, sheet styles, and modify other settings for your
|
||||
character.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-character-header-desktop__group--builder",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Character Builder"
|
||||
content={
|
||||
<>
|
||||
Click here to visit the Character Builder. Each time you level up,
|
||||
check here to see how to develop your character.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-main-mobile__abilities",
|
||||
".ct-main-tablet__abilities",
|
||||
".ct-quick-info__abilities"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Ability Scores"
|
||||
content={
|
||||
<>
|
||||
Much of what your character does in the game depends on his or her
|
||||
six abilities: Strength, Dexterity, Constitution, Intelligence,
|
||||
Wisdom, and Charisma. Learn more about an ability by clicking on
|
||||
it.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-combat-mobile__extra--proficiency",
|
||||
".ct-combat-tablet__extra--proficiency",
|
||||
".ct-quick-info__box--proficiency"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Proficiency Bonus"
|
||||
content={
|
||||
<>
|
||||
Proficiency is added to rolls made to accomplish tasks with which
|
||||
your character is proficient. The bonus is automatically added to
|
||||
rolls when it is needed. Click here to learn more about how
|
||||
Proficiency Bonuses are used.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-combat-mobile__extra--speed",
|
||||
".ct-combat-tablet__extra--speed",
|
||||
".ct-quick-info__box--speed"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Speed"
|
||||
content={
|
||||
<>
|
||||
In each round of combat, your character can move up to the total
|
||||
distance indicated by your speed. Click here to learn more or
|
||||
customize your speed.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-status-summary-mobile__health",
|
||||
".ct-status-summary-mobile__health",
|
||||
".ct-health-summary"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Hit Points"
|
||||
content={
|
||||
<>
|
||||
In this area, you can manage your character's hit points. When
|
||||
your character reaches zero hit points, they are on the brink of
|
||||
death and begin making death saving throws.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-combat-mobile__extra--initiative",
|
||||
".ct-combat-tablet__extra--initiative",
|
||||
".ct-combat__summary-group--initiative"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Initiative"
|
||||
content={
|
||||
<>
|
||||
Initiative rolls determine the order in which you go in combat.
|
||||
The higher the number, the sooner you get to fight. Click here to
|
||||
roll initiative!
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-combat-mobile__extra--ac",
|
||||
".ct-combat-tablet__extra--ac",
|
||||
".ct-combat__summary-group--ac"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Armor Class"
|
||||
content={
|
||||
<>
|
||||
Your Armor Class (AC) represents how well your character avoids
|
||||
being wounded in battle.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: getSelector(
|
||||
".ct-main-mobile__saving-throws",
|
||||
".ct-saving-throws-box",
|
||||
".ct-saving-throws-box"
|
||||
),
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Saving Throws"
|
||||
content={
|
||||
<>
|
||||
Your DM may ask you to roll a saving throw (or make a save) to
|
||||
resist an incoming effect. Click on a save to automatically roll.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(isDesktop || isTablet
|
||||
? [
|
||||
{
|
||||
selector: ".ct-proficiency-groups-box",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Proficiencies"
|
||||
content={
|
||||
<>
|
||||
Proficiencies tell you what tools, equipment, and languages
|
||||
your character is skilled at using.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-skills-box",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Skills"
|
||||
content={
|
||||
<>
|
||||
If you wish to perform an action, your DM will determine
|
||||
which of these skills you will use. Click on a skill to roll
|
||||
or learn more.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--skills",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Skills"
|
||||
content={
|
||||
<>
|
||||
If you wish to perform an action, your DM will determine
|
||||
which of these skills you will use. Click on a skill to roll
|
||||
or learn more.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]),
|
||||
...(!isDesktop
|
||||
? [
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--actions",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Actions"
|
||||
content={
|
||||
<>
|
||||
When you take your action on your turn, you can take one of
|
||||
the actions presented here. You can track actions or make
|
||||
rolls to perform them by clicking in this panel.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--equipment",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Inventory"
|
||||
content={
|
||||
<>
|
||||
View and manage your character's items and coin from this
|
||||
panel.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--spells",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Spells"
|
||||
content={
|
||||
<>
|
||||
If your character has the ability to cast spells, look here
|
||||
for a list of spells and to track your spellcasting.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--features",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Features & Traits"
|
||||
content={
|
||||
<>
|
||||
This section describes the source of your character's
|
||||
abilities. How this manifests in your character's
|
||||
personality is up to you.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(!isTablet
|
||||
? [
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--proficiencies",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Proficiencies"
|
||||
content={
|
||||
<>
|
||||
Proficiencies tell you what tools, equipment, and
|
||||
languages your character is skilled at using.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
selector: ".ct-quick-nav__menu-item--description",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Description"
|
||||
content={
|
||||
<>
|
||||
Use the description panel to tell your character's story.
|
||||
Your character's background features are also found here.
|
||||
</>
|
||||
}
|
||||
showClose
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
selector: ".ct-primary-box__tab--actions",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Actions"
|
||||
content={
|
||||
<>
|
||||
When you take your action on your turn, you can take one of
|
||||
the actions presented here. You can track actions or make
|
||||
rolls to perform them by clicking in this panel.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(hasSpells
|
||||
? [
|
||||
{
|
||||
selector: ".ct-primary-box__tab--spells",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Spells"
|
||||
content={
|
||||
<>
|
||||
If your character has the ability to cast spells, look
|
||||
here for a list of spells and to track your
|
||||
spellcasting.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
selector: ".ct-primary-box__tab--equipment",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Inventory"
|
||||
content={
|
||||
<>
|
||||
View and manage your character's items and coin from this
|
||||
panel.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-primary-box__tab--features",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Features & Traits"
|
||||
content={
|
||||
<>
|
||||
This section describes the source of your character's
|
||||
abilities. How this manifests in your character's
|
||||
personality is up to you.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
selector: ".ct-primary-box__tab--description",
|
||||
content: (
|
||||
<GuidedTourStep
|
||||
title="Description"
|
||||
content={
|
||||
<>
|
||||
Use the description panel to tell your character's story.
|
||||
Your character's background features are also found here.
|
||||
</>
|
||||
}
|
||||
showClose
|
||||
/>
|
||||
),
|
||||
},
|
||||
]),
|
||||
];
|
||||
return stepList.filter((s) => s);
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import CharacterSheetGuidedTour from "./CharacterSheetGuidedTour";
|
||||
|
||||
export default CharacterSheetGuidedTour;
|
||||
export { CharacterSheetGuidedTour };
|
||||
+8
-2
@@ -130,6 +130,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightAbilitiesSvg : DarkAbilitiesSvg,
|
||||
}}
|
||||
ContentComponent={MainMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="skills"
|
||||
@@ -139,6 +140,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightSkillsSvg : DarkSkillsSvg,
|
||||
}}
|
||||
ContentComponent={SkillsMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="actions"
|
||||
@@ -148,6 +150,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightActionsSvg : DarkActionsSvg,
|
||||
}}
|
||||
ContentComponent={ActionsMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="equipment"
|
||||
@@ -157,6 +160,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightEquipmentSvg : DarkEquipmentSvg,
|
||||
}}
|
||||
ContentComponent={EquipmentMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="spells"
|
||||
@@ -176,6 +180,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightFeaturesSvg : DarkFeaturesSvg,
|
||||
}}
|
||||
ContentComponent={FeaturesMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="proficiencies"
|
||||
@@ -187,6 +192,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
: DarkProficienciesSvg,
|
||||
}}
|
||||
ContentComponent={ProficiencyGroupsMobile}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="description"
|
||||
@@ -242,7 +248,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
]}
|
||||
>
|
||||
{(interpolatedStyles) => (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{interpolatedStyles.map((config) => (
|
||||
<Sidebar
|
||||
key={config.key}
|
||||
@@ -255,7 +261,7 @@ class CharacterSheetMobile extends React.PureComponent<Props, State> {
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
|
||||
+7
-3
@@ -43,7 +43,7 @@ import {
|
||||
} from "../../components/ComponentCarousel";
|
||||
import SectionPlaceholder from "../../components/SectionPlaceholder";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import BackdropStyles from "../BackdropStyles";
|
||||
import { BackdropStyles } from "../BackdropStyles";
|
||||
import CharacterHeaderTablet from "../CharacterHeaderTablet";
|
||||
import ActionsTablet from "../tablet/ActionsTablet";
|
||||
import CombatTablet from "../tablet/CombatTablet";
|
||||
@@ -125,6 +125,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightAbilitiesSvg : DarkAbilitiesSvg,
|
||||
}}
|
||||
ContentComponent={MainTablet}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="actions"
|
||||
@@ -134,6 +135,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightActionsSvg : DarkActionsSvg,
|
||||
}}
|
||||
ContentComponent={ActionsTablet}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="equipment"
|
||||
@@ -143,6 +145,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightEquipmentSvg : DarkEquipmentSvg,
|
||||
}}
|
||||
ContentComponent={EquipmentTablet}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="spells"
|
||||
@@ -162,6 +165,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
IconComponent: isDarkMode ? LightFeaturesSvg : DarkFeaturesSvg,
|
||||
}}
|
||||
ContentComponent={FeaturesTablet}
|
||||
isEnabled
|
||||
/>
|
||||
<ComponentCarouselItem
|
||||
itemKey="description"
|
||||
@@ -218,7 +222,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
]}
|
||||
>
|
||||
{(interpolatedStyles) => (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{interpolatedStyles.map((config) => (
|
||||
<Sidebar
|
||||
key={config.key}
|
||||
@@ -231,7 +235,7 @@ class CharacterSheetTablet extends React.PureComponent<Props, State> {
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { InitiativeBox } from "~/subApps/sheet/components/InitiativeBox";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
@@ -120,7 +120,9 @@ class Combat extends React.PureComponent<Props> {
|
||||
StyleComponent={BoxBackgroundComponent}
|
||||
theme={theme}
|
||||
/>
|
||||
<h2 style={visuallyHidden}>Defenses and Conditions</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>
|
||||
Defenses and Conditions
|
||||
</h2>
|
||||
<div
|
||||
className="ct-combat__statuses-group ct-combat__statuses-group--defenses"
|
||||
onClick={this.handleDefensesSummaryClick}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import clsx from "clsx";
|
||||
import React, { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
AlignmentContract,
|
||||
@@ -28,6 +28,7 @@ import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
|
||||
|
||||
@@ -45,7 +46,9 @@ import styles from "./styles.module.css";
|
||||
|
||||
const DEFAULT_VALUE = "--";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
isVertical: boolean;
|
||||
background: Background | null;
|
||||
alignment: AlignmentContract | null;
|
||||
@@ -187,14 +190,9 @@ class Description extends React.PureComponent<Props> {
|
||||
traits,
|
||||
} = this.props;
|
||||
|
||||
const infoItemProps = {
|
||||
role: "listitem",
|
||||
inline: isVertical,
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="ct-description">
|
||||
<h2 style={visuallyHidden}>Description</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Description</h2>
|
||||
<TabFilter
|
||||
filters={[
|
||||
{
|
||||
@@ -240,34 +238,34 @@ class Description extends React.PureComponent<Props> {
|
||||
)
|
||||
}
|
||||
>
|
||||
<InfoItem label="Alignment" {...infoItemProps}>
|
||||
<InfoItem label="Alignment" inline={isVertical}>
|
||||
{alignment === null ? DEFAULT_VALUE : alignment.name}
|
||||
</InfoItem>
|
||||
<InfoItem label="Gender" {...infoItemProps}>
|
||||
<InfoItem label="Gender" inline={isVertical}>
|
||||
{this.renderDescriptionItem(gender)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Eyes" {...infoItemProps}>
|
||||
<InfoItem label="Eyes" inline={isVertical}>
|
||||
{this.renderDescriptionItem(eyes)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Size" {...infoItemProps}>
|
||||
<InfoItem label="Size" inline={isVertical}>
|
||||
{this.renderDescriptionItem(size ? size.name : null)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Height" {...infoItemProps}>
|
||||
<InfoItem label="Height" inline={isVertical}>
|
||||
{this.renderDescriptionItem(height)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Faith" {...infoItemProps}>
|
||||
<InfoItem label="Faith" inline={isVertical}>
|
||||
{this.renderDescriptionItem(faith)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Hair" {...infoItemProps}>
|
||||
<InfoItem label="Hair" inline={isVertical}>
|
||||
{this.renderDescriptionItem(hair)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Skin" {...infoItemProps}>
|
||||
<InfoItem label="Skin" inline={isVertical}>
|
||||
{this.renderDescriptionItem(skin)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Age" {...infoItemProps}>
|
||||
<InfoItem label="Age" inline={isVertical}>
|
||||
{this.renderDescriptionItem(age)}
|
||||
</InfoItem>
|
||||
<InfoItem label="Weight" {...infoItemProps}>
|
||||
<InfoItem label="Weight" inline={isVertical}>
|
||||
{weight === null ? (
|
||||
DEFAULT_VALUE
|
||||
) : (
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { useContext, ReactNode, MouseEvent, PureComponent } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useContext, ReactNode, PureComponent } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { DarkBuilderSvg } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
DarkBuilderSvg,
|
||||
FeatureFlagContext,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterCurrencyContract,
|
||||
CharacterNotes,
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
@@ -33,23 +18,29 @@ import {
|
||||
SnippetData,
|
||||
RuleData,
|
||||
rulesEngineSelectors,
|
||||
characterSelectors,
|
||||
BaseCharClass,
|
||||
ContainerUtils,
|
||||
InventoryManager,
|
||||
FormatUtils,
|
||||
CampaignUtils,
|
||||
serviceDataSelectors,
|
||||
PartyInfo,
|
||||
CoinManager,
|
||||
ClassUtils,
|
||||
CharClass,
|
||||
ItemPlan,
|
||||
ItemPlanUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
import { ItemName } from "~/components/ItemName";
|
||||
import { Link } from "~/components/Link";
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { TabList } from "~/components/TabList";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { EquipmentOverview } from "~/subApps/sheet/components/Equipment/EquipmentOverview";
|
||||
import { MagicItemPlans } from "~/subApps/sheet/components/MagicItemPlans";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import CurrencyButton from "../../../CharacterSheet/components/CurrencyButton";
|
||||
@@ -60,7 +51,6 @@ import { InventoryManagerContext } from "../../../Shared/managers/InventoryManag
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import { PaneIdentifierUtils } from "../../../Shared/utils";
|
||||
import ContentGroup from "../../components/ContentGroup";
|
||||
import EquipmentOverview from "../../components/EquipmentOverview";
|
||||
import Infusions from "../../components/Infusions";
|
||||
import InventoryFilter from "../../components/InventoryFilter";
|
||||
import InventoryTableHeader from "../../components/InventoryTableHeader";
|
||||
@@ -69,6 +59,7 @@ import { sheetAppSelectors } from "../../selectors";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import Attunement from "../Attunement";
|
||||
import Inventory from "../Inventory";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
showNotes: boolean;
|
||||
@@ -77,22 +68,21 @@ interface Props {
|
||||
inventory: Array<Item>;
|
||||
partyInventory: Array<Item>;
|
||||
creatures: Array<Creature>;
|
||||
currencies: CharacterCurrencyContract | null;
|
||||
notes: CharacterNotes;
|
||||
infusionChoices: Array<InfusionChoice>;
|
||||
weight: number;
|
||||
weightSpeedType: Constants.WeightSpeedTypeEnum;
|
||||
ruleData: RuleData;
|
||||
snippetData: SnippetData;
|
||||
isReadonly: boolean;
|
||||
isMobile: boolean;
|
||||
theme: CharacterTheme;
|
||||
proficiencyBonus: number;
|
||||
classes: Array<BaseCharClass>;
|
||||
classes: Array<CharClass>;
|
||||
inventoryManager: InventoryManager;
|
||||
campaignInfo: PartyInfo | null;
|
||||
coinManager: CoinManager;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
itemPlans: Array<ItemPlan>;
|
||||
maxReplicatedItemsCount: number | null;
|
||||
}
|
||||
interface StateFilterData {
|
||||
filteredInventory: Array<Item>;
|
||||
@@ -105,7 +95,6 @@ interface State {
|
||||
filterData: StateFilterData;
|
||||
filteredEquippedInventory: Array<Item>;
|
||||
shouldShowPartyInventory: number;
|
||||
shouldShowDeleteOnlyInfo: boolean;
|
||||
}
|
||||
|
||||
class Equipment extends PureComponent<Props, State> {
|
||||
@@ -128,7 +117,6 @@ class Equipment extends PureComponent<Props, State> {
|
||||
},
|
||||
filteredEquippedInventory: [],
|
||||
shouldShowPartyInventory: 0, // mui used 0,1,2 for tabs so 0 is off 1 is on...
|
||||
shouldShowDeleteOnlyInfo: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -157,16 +145,6 @@ class Equipment extends PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
handleWeightClick = (): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
paneHistoryStart(PaneComponentEnum.ENCUMBRANCE);
|
||||
};
|
||||
|
||||
handleCampaignClick = (): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
paneHistoryStart(PaneComponentEnum.CAMPAIGN);
|
||||
};
|
||||
|
||||
handlePossessionsManage = (): void => {
|
||||
const { paneHistoryStart, isReadonly } = this.props;
|
||||
|
||||
@@ -233,12 +211,26 @@ class Equipment extends PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
handleItemPlansManagePaneShow = (): void => {
|
||||
const { paneHistoryStart } = this.props;
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.ITEM_PLANS_MANAGE);
|
||||
};
|
||||
|
||||
shouldRenderInfusions = (): boolean => {
|
||||
const { infusionChoices } = this.props;
|
||||
|
||||
return infusionChoices.length > 0;
|
||||
};
|
||||
|
||||
shouldRenderMagicItemPlans = (): boolean => {
|
||||
const { classes } = this.props;
|
||||
|
||||
return classes.some((charClass) =>
|
||||
ClassUtils.getEnablesReplicateMagicItem(charClass)
|
||||
);
|
||||
};
|
||||
|
||||
renderContainer = (
|
||||
container: ContainerManager,
|
||||
inventory: Array<Item>
|
||||
@@ -380,7 +372,7 @@ class Equipment extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderAttunement = (): ReactNode => {
|
||||
const { isMobile, theme } = this.props;
|
||||
const { isMobile } = this.props;
|
||||
|
||||
return (
|
||||
<ContentGroup header="Attunement">
|
||||
@@ -389,6 +381,54 @@ class Equipment extends PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
renderMagicItemPlans = (): ReactNode => {
|
||||
const { isReadonly, itemPlans, maxReplicatedItemsCount } = this.props;
|
||||
|
||||
if (!this.shouldRenderMagicItemPlans()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const itemPlanWithMappingsCount = itemPlans.filter((plan) =>
|
||||
ItemPlanUtils.getItem(plan)
|
||||
).length;
|
||||
|
||||
const headerNode: ReactNode = (
|
||||
<>
|
||||
Magic Item Plans{" "}
|
||||
{maxReplicatedItemsCount !== null && (
|
||||
<span className={styles.replicatedCount}>
|
||||
({itemPlanWithMappingsCount}/{maxReplicatedItemsCount} Replicated)
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
let extraNode: ReactNode = null;
|
||||
if (!isReadonly) {
|
||||
extraNode = (
|
||||
<Button
|
||||
variant="text"
|
||||
themed
|
||||
size="x-small"
|
||||
className="ct-equipment__builder-link-text"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.handleItemPlansManagePaneShow();
|
||||
}}
|
||||
>
|
||||
Manage Plans
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentGroup header={headerNode} extra={extraNode}>
|
||||
<MagicItemPlans />
|
||||
</ContentGroup>
|
||||
);
|
||||
};
|
||||
|
||||
renderInfusions = (): ReactNode => {
|
||||
const { infusableChoices } = this.state;
|
||||
const {
|
||||
@@ -421,7 +461,7 @@ class Equipment extends PureComponent<Props, State> {
|
||||
<Link
|
||||
href={builderUrl}
|
||||
className="ct-equipment__builder-link"
|
||||
useRouter
|
||||
userouter
|
||||
>
|
||||
<DarkBuilderSvg />
|
||||
<span className="ct-equipment__builder-link-text">
|
||||
@@ -490,7 +530,7 @@ class Equipment extends PureComponent<Props, State> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ct-equipment__content">
|
||||
<div className={styles.tabFilter}>
|
||||
<TabFilter
|
||||
sharedChildren={<InventoryTableHeader showNotes={showNotes} />}
|
||||
filters={[
|
||||
@@ -503,20 +543,16 @@ class Equipment extends PureComponent<Props, State> {
|
||||
{this.renderCharacterContainers()}
|
||||
{this.renderAttunement()}
|
||||
{this.shouldRenderInfusions() && this.renderInfusions()}
|
||||
{this.renderMagicItemPlans()}
|
||||
{this.renderOtherPossessions()}
|
||||
</>
|
||||
) : null}
|
||||
<FeatureFlagContext.Consumer>
|
||||
{({ imsFlag }) => {
|
||||
return imsFlag &&
|
||||
shouldShowPartyInventory === 1 &&
|
||||
inventoryManager.getPartyContainers().length > 0 ? (
|
||||
<ContentGroup header={headerNode}>
|
||||
{this.renderPartyContainers()}
|
||||
</ContentGroup>
|
||||
) : null;
|
||||
}}
|
||||
</FeatureFlagContext.Consumer>
|
||||
{shouldShowPartyInventory === 1 &&
|
||||
inventoryManager.getPartyContainers().length > 0 ? (
|
||||
<ContentGroup header={headerNode}>
|
||||
{this.renderPartyContainers()}
|
||||
</ContentGroup>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -551,6 +587,14 @@ class Equipment extends PureComponent<Props, State> {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(this.shouldRenderMagicItemPlans()
|
||||
? [
|
||||
{
|
||||
label: "Magic Item Plans",
|
||||
content: this.renderMagicItemPlans(),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!isReadonly
|
||||
? [
|
||||
{
|
||||
@@ -592,126 +636,17 @@ class Equipment extends PureComponent<Props, State> {
|
||||
shouldShowPartyInventory: newValue,
|
||||
});
|
||||
};
|
||||
handleCloseDeleteOnlyInfo = (event: MouseEvent): void => {
|
||||
event.stopPropagation();
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
this.setState({
|
||||
shouldShowDeleteOnlyInfo: false,
|
||||
});
|
||||
};
|
||||
handleOpenDeleteOnlyInfo = (event: MouseEvent): void => {
|
||||
event.stopPropagation();
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
this.setState({
|
||||
shouldShowDeleteOnlyInfo: true,
|
||||
});
|
||||
};
|
||||
|
||||
renderDeleteOnlyAlertAction = (): ReactNode => {
|
||||
const { shouldShowDeleteOnlyInfo } = this.state;
|
||||
const { campaignInfo } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ThemeButton
|
||||
style="outline"
|
||||
size="medium"
|
||||
onClick={this.handleOpenDeleteOnlyInfo}
|
||||
>
|
||||
More Info
|
||||
</ThemeButton>
|
||||
<Dialog
|
||||
open={shouldShowDeleteOnlyInfo}
|
||||
onClose={this.handleCloseDeleteOnlyInfo}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Only</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Party Inventory sharing isn’t currently enabled for this campaign.
|
||||
Your old items are still here, but you won’t be able to add new
|
||||
items until it’s turned on again.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{campaignInfo ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
component="a"
|
||||
href={CampaignUtils.getLink(campaignInfo) || "/my-campaigns"}
|
||||
size="small"
|
||||
>
|
||||
go to campaign
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={this.handleCloseDeleteOnlyInfo}
|
||||
autoFocus
|
||||
size="small"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderOverviewFiltersAndContent = (): ReactNode => {
|
||||
const { filterData, shouldShowPartyInventory } = this.state;
|
||||
const {
|
||||
currencies,
|
||||
weight,
|
||||
weightSpeedType,
|
||||
inventory,
|
||||
ruleData,
|
||||
theme,
|
||||
partyInventory,
|
||||
campaignInfo,
|
||||
inventoryManager,
|
||||
coinManager,
|
||||
} = this.props;
|
||||
|
||||
const cointainerFlagEnabled: boolean = coinManager.canUseCointainers();
|
||||
let coin: CharacterCurrencyContract | null = currencies;
|
||||
if (shouldShowPartyInventory === 1 && campaignInfo) {
|
||||
// We need all the party coin if flag is on, or just Party Equipment coin if not
|
||||
if (cointainerFlagEnabled) {
|
||||
coin = coinManager.getAllPartyCoin();
|
||||
} else {
|
||||
coin = CampaignUtils.getCoin(campaignInfo);
|
||||
}
|
||||
} else if (cointainerFlagEnabled) {
|
||||
// We need to change it to all container currencies
|
||||
coin = coinManager.getAllCharacterCoin();
|
||||
}
|
||||
const { inventory, ruleData, theme, partyInventory } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ct-equipment__overview">
|
||||
<EquipmentOverview
|
||||
shouldShowPartyInventory={shouldShowPartyInventory === 1}
|
||||
currencies={coin}
|
||||
weight={weight}
|
||||
weightSpeedType={weightSpeedType}
|
||||
onCurrencyClick={(evt) =>
|
||||
this.handleCurrencyClick(
|
||||
evt,
|
||||
shouldShowPartyInventory === 1
|
||||
? inventoryManager.getPartyEquipmentContainerDefinitionKey() ??
|
||||
inventoryManager.getCharacterContainerDefinitionKey()
|
||||
: inventoryManager.getCharacterContainerDefinitionKey()
|
||||
)
|
||||
}
|
||||
onWeightClick={this.handleWeightClick}
|
||||
onCampaignClick={this.handleCampaignClick}
|
||||
enableManage={false}
|
||||
isDarkMode={theme.isDarkMode}
|
||||
campaignName={
|
||||
campaignInfo !== null ? CampaignUtils.getName(campaignInfo) : null
|
||||
activeEquipmentTab={
|
||||
shouldShowPartyInventory === 1 ? "party" : "character"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -730,113 +665,39 @@ class Equipment extends PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
renderOptinToInventorySharing = (): ReactNode => {
|
||||
handleChangeTab = (id) =>
|
||||
this.setState({
|
||||
shouldShowPartyInventory: id === "party-inventory" ? 1 : 0,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { campaignInfo } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack justifyContent="center">
|
||||
<Stack marginBottom="10px" marginTop="40px">
|
||||
<Typography color="primary" align="center" variant="body2">
|
||||
Want a hand with that loot?
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack marginBottom="30px" alignItems={"center"}>
|
||||
<Typography
|
||||
align="center"
|
||||
variant="body1"
|
||||
sx={{ maxWidth: "320px" }}
|
||||
>
|
||||
Party Inventory makes it easy for your group to keep track of all
|
||||
their shared loot and coin. Add it to your campaign today with a
|
||||
subscription to D&D Beyond!
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
spacing={2}
|
||||
direction={{ xs: "column", sm: "row" }}
|
||||
justifyContent="center"
|
||||
>
|
||||
{campaignInfo ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component="a"
|
||||
href={CampaignUtils.getLink(campaignInfo) || "/my-campaigns"}
|
||||
size="large"
|
||||
sx={{
|
||||
marginLeft: { xs: 0 },
|
||||
marginRight: { xs: 0 },
|
||||
}}
|
||||
>
|
||||
go to campaign
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button variant="contained" color="primary" size="large">
|
||||
view subscription plans
|
||||
</Button>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={{ display: "flex", justifyContent: "center", marginTop: 51 }}
|
||||
>
|
||||
<div className="ct-item-detail__full-image">
|
||||
<img
|
||||
className="ct-item-detail__full-image-img"
|
||||
src="https://www.dndbeyond.com/avatars/thumbnails/7/120/315/315/636284708068284913.jpeg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { shouldShowPartyInventory } = this.state;
|
||||
const { campaignInfo, inventoryManager } = this.props;
|
||||
|
||||
return (
|
||||
<section className="ct-equipment">
|
||||
<h2 style={visuallyHidden}>Inventory</h2>
|
||||
<FeatureFlagContext.Consumer>
|
||||
{({ imsFlag }) => {
|
||||
return imsFlag && campaignInfo !== null ? (
|
||||
<>
|
||||
<Tabs
|
||||
value={shouldShowPartyInventory}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={this.toggleShouldShowPartyInventory}
|
||||
aria-label="disabled tabs example"
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab label="My Inventory" data-testid="my-inventory" />
|
||||
<Tab label="Party Inventory" data-testid="party-inventory" />
|
||||
</Tabs>
|
||||
{/* TODO: make this go away when last itme is removed... */}
|
||||
{shouldShowPartyInventory &&
|
||||
inventoryManager.isSharingTurnedDeleteOnly() ? (
|
||||
<div role="button" onMouseUp={this.handleOpenDeleteOnlyInfo}>
|
||||
<Alert
|
||||
severity="info"
|
||||
action={this.renderDeleteOnlyAlertAction()}
|
||||
>
|
||||
Party Inventory is currently turned off for this campaign.
|
||||
</Alert>
|
||||
</div>
|
||||
) : null}
|
||||
{shouldShowPartyInventory &&
|
||||
inventoryManager.isSharingTurnedOff()
|
||||
? this.renderOptinToInventorySharing()
|
||||
: this.renderOverviewFiltersAndContent()}
|
||||
</>
|
||||
) : (
|
||||
this.renderOverviewFiltersAndContent()
|
||||
);
|
||||
}}
|
||||
</FeatureFlagContext.Consumer>
|
||||
<section className={clsx(["ct-equipment", styles.section])}>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Inventory</h2>
|
||||
{campaignInfo !== null ? (
|
||||
<TabList
|
||||
className={styles.tabList}
|
||||
variant="fullwidth"
|
||||
themed={true}
|
||||
tabs={[
|
||||
{
|
||||
label: "My Inventory",
|
||||
content: this.renderOverviewFiltersAndContent(),
|
||||
id: "my-inventory",
|
||||
},
|
||||
{
|
||||
label: "Party Inventory",
|
||||
content: this.renderOverviewFiltersAndContent(),
|
||||
id: "party-inventory",
|
||||
},
|
||||
]}
|
||||
onChangeTab={this.handleChangeTab}
|
||||
/>
|
||||
) : (
|
||||
this.renderOverviewFiltersAndContent()
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -848,19 +709,19 @@ function mapStateToProps(state: SheetAppState) {
|
||||
inventory: rulesEngineSelectors.getInventory(state),
|
||||
partyInventory: rulesEngineSelectors.getPartyInventory(state),
|
||||
creatures: rulesEngineSelectors.getCreatures(state),
|
||||
weight: rulesEngineSelectors.getTotalCarriedWeight(state),
|
||||
weightSpeedType: rulesEngineSelectors.getCurrentCarriedWeightType(state),
|
||||
notes: rulesEngineSelectors.getCharacterNotes(state),
|
||||
infusionChoices: rulesEngineSelectors.getAvailableInfusionChoices(state),
|
||||
currencies: rulesEngineSelectors.getCurrencies(state),
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
snippetData: rulesEngineSelectors.getSnippetData(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
isMobile: appEnvSelectors.getIsMobile(state),
|
||||
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
|
||||
classes: characterSelectors.getClasses(state),
|
||||
classes: rulesEngineSelectors.getClasses(state),
|
||||
campaignInfo: serviceDataSelectors.getPartyInfo(state),
|
||||
itemPlans: rulesEngineSelectors.getAvailableItemPlans(state),
|
||||
maxReplicatedItemsCount:
|
||||
rulesEngineSelectors.getMaxReplicatedItemsCount(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React, { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
@@ -16,6 +16,7 @@ import { TabFilter } from "~/components/TabFilter";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { useExtras } from "~/hooks/useExtras";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import ExtraList from "../../../Shared/components/ExtraList";
|
||||
@@ -27,7 +28,9 @@ import ContentGroup from "../../components/ContentGroup";
|
||||
import ExtrasFilter from "../../components/ExtrasFilter";
|
||||
import { SheetAppState } from "../../typings";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
extras: Array<ExtraManager>;
|
||||
extrasManager: ExtrasManager;
|
||||
isReadonly: boolean;
|
||||
@@ -229,7 +232,7 @@ class Extras extends React.PureComponent<Props, State> {
|
||||
theme.isDarkMode ? "ct-extras--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
<h2 style={visuallyHidden}>Extras</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Extras</h2>
|
||||
<div className="ct-extras__filter">
|
||||
<ExtrasFilter
|
||||
extras={extras}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import React, { useContext } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
InfusionChoiceUtils,
|
||||
RacialTrait,
|
||||
RacialTraitUtils,
|
||||
FeaturesManager,
|
||||
FeatManager,
|
||||
Action,
|
||||
ActionUtils,
|
||||
@@ -18,8 +16,9 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { useFeatureFlags } from "~/contexts/FeatureFlag";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { SpeciesDetail } from "~/subApps/sheet/components/SpeciesDetail";
|
||||
import { ThemeButton } from "~/tools/js/Shared/components/common/Button";
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
import { CharacterFeaturesManagerContext } from "../../../Shared/managers/CharacterFeaturesManagerContext";
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import { PaneIdentifierUtils } from "../../../Shared/utils";
|
||||
import BlessingsDetail from "../../components/BlessingsDetail";
|
||||
import ClassesDetail from "../../components/ClassesDetail";
|
||||
import ContentGroup from "../../components/ContentGroup";
|
||||
import FeatsDetail from "../../components/FeatsDetail";
|
||||
@@ -230,7 +228,6 @@ const ClassFeaturesGroup: React.FC<{}> = () => {
|
||||
|
||||
const FeatsGroup: React.FC<{}> = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { gfsBlessingsUiFlag } = useFeatureFlags();
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
} = useSidebar();
|
||||
@@ -285,7 +282,7 @@ const FeatsGroup: React.FC<{}> = () => {
|
||||
style="outline"
|
||||
size="medium"
|
||||
>
|
||||
Manage {gfsBlessingsUiFlag ? "Features" : "Feats"}
|
||||
Manage Feats
|
||||
</ThemeButton>
|
||||
</div>
|
||||
)}
|
||||
@@ -311,35 +308,10 @@ const FeatsGroup: React.FC<{}> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const BlessingsGroup: React.FC<{}> = () => {
|
||||
return (
|
||||
<ContentGroup header="Blessings">
|
||||
<BlessingsDetail />
|
||||
</ContentGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const Features: React.FC<{}> = () => {
|
||||
const { gfsBlessingsUiFlag } = useFeatureFlags();
|
||||
const { characterFeaturesManager } = useContext(
|
||||
CharacterFeaturesManagerContext
|
||||
);
|
||||
|
||||
const [hasBlessings, setHasBlessings] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function onUpdate() {
|
||||
const hasBlessings = gfsBlessingsUiFlag
|
||||
? await characterFeaturesManager.hasBlessings()
|
||||
: false;
|
||||
setHasBlessings(hasBlessings);
|
||||
}
|
||||
return FeaturesManager.subscribeToUpdates({ onUpdate });
|
||||
}, [setHasBlessings]);
|
||||
|
||||
return (
|
||||
<section className="ct-features">
|
||||
<h2 style={visuallyHidden}>Features and Traits</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Features and Traits</h2>
|
||||
<TabFilter
|
||||
filters={[
|
||||
{
|
||||
@@ -354,9 +326,6 @@ const Features: React.FC<{}> = () => {
|
||||
label: "Feats",
|
||||
content: <FeatsGroup />,
|
||||
},
|
||||
...(gfsBlessingsUiFlag && hasBlessings
|
||||
? [{ label: "Blessings", content: <BlessingsGroup /> }]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useContext, useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { ThemeButton } from "../../../Shared/components/common/Button";
|
||||
import { InventoryManagerContext } from "../../../Shared/managers/InventoryManagerContext";
|
||||
import { PaneIdentifierUtils } from "../../../Shared/utils";
|
||||
import InventoryItem from "../../components/InventoryItem";
|
||||
import { InventoryTableHeader } from "../../components/InventoryTableHeader";
|
||||
@@ -42,8 +42,6 @@ const InventoryActions: React.FC<InventoryActionsProps> = ({
|
||||
onShowContentsClick,
|
||||
showContents,
|
||||
}) => {
|
||||
const { inventoryManager } = useContext(InventoryManagerContext);
|
||||
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
@@ -57,24 +55,20 @@ const InventoryActions: React.FC<InventoryActionsProps> = ({
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")}>
|
||||
{inventoryManager.canAddToContainer(container.container) ? (
|
||||
<span
|
||||
role="button"
|
||||
className="ct-inventory__action"
|
||||
onClick={(evt) => onContainerClick(evt, container, true)}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
onContainerClick(evt, container, true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{container.isCharacterContainer()
|
||||
? `+ Add ${containerName}`
|
||||
: `+ Add items to your ${containerName}`}
|
||||
</span>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<span
|
||||
role="button"
|
||||
className="ct-inventory__action"
|
||||
onClick={(evt) => onContainerClick(evt, container, true)}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
onContainerClick(evt, container, true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{container.isCharacterContainer()
|
||||
? `+ Add ${containerName}`
|
||||
: `+ Add items to your ${containerName}`}
|
||||
</span>
|
||||
{inventory.length > 0 && (
|
||||
<span
|
||||
role="button"
|
||||
@@ -192,7 +186,7 @@ const Inventory: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="ct-inventory">
|
||||
{/*<h2 style={visuallyHidden}>Inventory</h2>*/}
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Inventory</h2>
|
||||
{showTableHeader && <InventoryTableHeader showNotes={showNotes} />}
|
||||
<div className="ct-inventory__items">
|
||||
{isEmpty ? (
|
||||
@@ -205,7 +199,7 @@ const Inventory: React.FC<Props> = ({
|
||||
onContainerClick={handleContainerShow}
|
||||
/>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{showContents &&
|
||||
inventory.map((item) => (
|
||||
<InventoryItem
|
||||
@@ -232,7 +226,7 @@ const Inventory: React.FC<Props> = ({
|
||||
onShowContentsClick={setShowContents}
|
||||
showContents={showContents}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
@@ -71,7 +71,7 @@ class Notes extends React.PureComponent<NotesProps> {
|
||||
render() {
|
||||
return (
|
||||
<section className="ct-notes">
|
||||
<h2 style={visuallyHidden}>Notes</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Notes</h2>
|
||||
<TabFilter
|
||||
filters={[
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
BeveledBoxSvg517x660,
|
||||
@@ -39,7 +41,9 @@ const TAB_KEY = {
|
||||
EXTRAS: "EXTRAS",
|
||||
};
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
hasSpells: boolean;
|
||||
dimensions: AppEnvDimensionsState;
|
||||
theme: CharacterTheme;
|
||||
@@ -62,9 +66,11 @@ class PrimaryBox extends React.PureComponent<Props, {}> {
|
||||
return (
|
||||
<Subsection name="Primary Box">
|
||||
<div
|
||||
className={`ct-primary-box ${
|
||||
theme.isDarkMode ? "ct-primary-box--dark-mode" : ""
|
||||
}`}
|
||||
className={clsx([
|
||||
"ct-primary-box",
|
||||
theme.isDarkMode && "ct-primary-box--dark-mode",
|
||||
styles.primaryBox,
|
||||
])}
|
||||
>
|
||||
<BoxBackground
|
||||
StyleComponent={BoxBackgroundComponent}
|
||||
@@ -122,6 +128,7 @@ class PrimaryBox extends React.PureComponent<Props, {}> {
|
||||
id: TAB_KEY.EXTRAS,
|
||||
},
|
||||
]}
|
||||
themed
|
||||
/>
|
||||
</div>
|
||||
</Subsection>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { AbilitySummary } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -10,6 +9,8 @@ import {
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useAbilities } from "~/hooks/useAbilities";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { HitPointsBox } from "~/subApps/sheet/components/HitPointsBox/HitPointsBox";
|
||||
import { Inspiration } from "~/subApps/sheet/components/Inspiration";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
@@ -76,7 +77,7 @@ export default function QuickInfo() {
|
||||
return (
|
||||
<div className="ct-quick-info">
|
||||
<section className="ct-quick-info__abilities">
|
||||
<h2 style={visuallyHidden}>Ability Scores</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Ability Scores</h2>
|
||||
{abilities.map((ability) => (
|
||||
<div className="ct-quick-info__ability" key={ability.getStatKey()}>
|
||||
<AbilitySummary
|
||||
@@ -107,7 +108,7 @@ export default function QuickInfo() {
|
||||
/>
|
||||
</div>
|
||||
<section className="ct-quick-info__inspiration">
|
||||
<h2 style={visuallyHidden}>Inspiration</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Inspiration</h2>
|
||||
<Inspiration
|
||||
inspiration={!!inspiration}
|
||||
onToggle={handleToggleInspiration}
|
||||
|
||||
+2
-2
@@ -30,10 +30,10 @@ export default function SavingThrowsDesktop() {
|
||||
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
||||
const savingThrowDiceAdjustments = useSelector(
|
||||
rulesEngineSelectors.getSavingThrowDiceAdjustments
|
||||
); // TODO: GFS move to mangers
|
||||
);
|
||||
const situationalBonusSavingThrowsLookup = useSelector(
|
||||
rulesEngineSelectors.getSituationalBonusSavingThrowsLookup
|
||||
); // TODO: GFS move to mangers
|
||||
);
|
||||
const deathSaveInfo = useSelector(rulesEngineSelectors.getDeathSaveInfo);
|
||||
const dimensions = useSelector(appEnvSelectors.getDimensions);
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
CharacterTheme,
|
||||
RuleData,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
@@ -36,7 +36,7 @@ interface Props {
|
||||
isReadonly: boolean;
|
||||
diceEnabled: boolean;
|
||||
ruleData: RuleData;
|
||||
characterRollContext: IRollContext;
|
||||
characterRollContext: RollContext;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
}
|
||||
class SkillsDesktop extends React.PureComponent<Props> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
ApiRequestHelpers,
|
||||
@@ -12,7 +13,9 @@ import { appEnvSelectors } from "../../../Shared/selectors";
|
||||
import { SheetAppState } from "../../typings";
|
||||
import SpellSlotManagerGroup from "./SpellSlotManagerGroup";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
spellSlots: Array<SpellSlotContract>;
|
||||
pactSlots: Array<SpellSlotContract>;
|
||||
isReadonly: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
ConcentrationIcon,
|
||||
@@ -28,10 +28,11 @@ import {
|
||||
SpellSlotContract,
|
||||
SpellUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { TabFilter } from "~/components/TabFilter";
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import SpellsLevel from "../../../Shared/components/SpellsLevel";
|
||||
@@ -70,7 +71,9 @@ function hasFilteredLevelSpells(
|
||||
}, false);
|
||||
}
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
levelSpells: Array<Array<ScaledSpell>>;
|
||||
spellCasterInfo: SpellCasterInfo;
|
||||
ruleData: RuleData;
|
||||
@@ -84,7 +87,7 @@ interface Props extends DispatchProp {
|
||||
diceEnabled: boolean;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
characterRollContext: IRollContext;
|
||||
characterRollContext: RollContext;
|
||||
inventoryManager: InventoryManager;
|
||||
inventoryLookup: InventoryLookup;
|
||||
partyInventoryLookup: InventoryLookup;
|
||||
@@ -352,7 +355,7 @@ class Spells extends React.PureComponent<Props, State> {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{levelSpells.map((spells, level) => {
|
||||
if (!showLevels.includes(ALL_LEVELS) && !showLevels.includes(level)) {
|
||||
return null;
|
||||
@@ -438,7 +441,7 @@ class Spells extends React.PureComponent<Props, State> {
|
||||
</ContentGroup>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -449,7 +452,7 @@ class Spells extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<section className="ct-spells">
|
||||
<h2 style={visuallyHidden}>Spells</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Spells</h2>
|
||||
<div className="ct-spells__casting">
|
||||
<SpellsLevelCasting
|
||||
spellCasterInfo={spellCasterInfo}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import MobileDivider from "../../../components/MobileDivider";
|
||||
import SubsectionMobile from "../../../components/SubsectionMobile";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Actions from "../../Actions";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class ActionsMobile extends React.PureComponent<Props> {
|
||||
@@ -21,12 +25,12 @@ class ActionsMobile extends React.PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<SubsectionMobile>
|
||||
<MobileDivider label="Actions" theme={theme} />
|
||||
<section className="ct-actions-mobile">
|
||||
<h2 style={visuallyHidden}>Actions</h2>
|
||||
<MobileDivider label="Actions" theme={theme} />
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Actions</h2>
|
||||
<Actions showNotes={false} />
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
</SubsectionMobile>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterPreferences,
|
||||
@@ -25,7 +26,9 @@ import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
armorClass: number;
|
||||
speeds: SpeedInfo;
|
||||
proficiencyBonus: number;
|
||||
@@ -232,6 +235,8 @@ function mapStateToProps(state: SheetAppState) {
|
||||
};
|
||||
}
|
||||
|
||||
CombatMobile.contextType = GameLogContext;
|
||||
|
||||
const CombatMobileContainer = (props) => {
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
@@ -239,6 +244,4 @@ const CombatMobileContainer = (props) => {
|
||||
return <CombatMobile {...props} paneHistoryStart={paneHistoryStart} />;
|
||||
};
|
||||
|
||||
CombatMobileContainer.contextType = GameLogContext;
|
||||
|
||||
export default connect(mapStateToProps)(CombatMobileContainer);
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { FC } from "react";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import MobileDivider from "../../../components/MobileDivider";
|
||||
import SubsectionMobile from "../../../components/SubsectionMobile";
|
||||
@@ -13,7 +13,7 @@ export const DescriptionMobile: FC = () => {
|
||||
<SubsectionMobile className="ct-description-mobile">
|
||||
<MobileDivider label={"Background"} theme={characterTheme} />
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Background</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Background</h2>
|
||||
<Description theme={characterTheme} isVertical={true} />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={characterTheme} />
|
||||
|
||||
+2
-2
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -42,7 +42,7 @@ class EquipmentMobile extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
/>
|
||||
<section className="ct-equipment-mobile">
|
||||
<h2 style={visuallyHidden}>Inventory</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Inventory</h2>
|
||||
<Equipment showNotes={false} />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -42,7 +42,7 @@ class ExtrasMobile extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
/>
|
||||
<section className="ct-extras-mobile">
|
||||
<h2 style={visuallyHidden}>Extras</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Extras</h2>
|
||||
<Extras showNotes={false} />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
+7
-3
@@ -1,18 +1,22 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import MobileDivider from "../../../components/MobileDivider";
|
||||
import SubsectionMobile from "../../../components/SubsectionMobile";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Features from "../../Features";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class FeaturesMobile extends React.PureComponent<Props> {
|
||||
@@ -23,7 +27,7 @@ class FeaturesMobile extends React.PureComponent<Props> {
|
||||
<SubsectionMobile>
|
||||
<MobileDivider label="Features & Traits" theme={theme} />
|
||||
<section className="ct-features-mobile">
|
||||
<h2 style={visuallyHidden}>Features and Traits</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Features and Traits</h2>
|
||||
<Features />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import {
|
||||
AbilitySummary,
|
||||
CampaignSummary,
|
||||
SavingThrowsSummary,
|
||||
ThemedLongRestSvg,
|
||||
ThemedShortRestSvg,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
AbilityManager,
|
||||
@@ -12,8 +14,10 @@ import {
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useAbilities } from "~/hooks/useAbilities";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { getDataOriginComponentInfo } from "../../../../../../subApps/sheet/components/Sidebar/helpers/paneUtils";
|
||||
import { PaneComponentEnum } from "../../../../../../subApps/sheet/components/Sidebar/types";
|
||||
@@ -26,6 +30,7 @@ import MobileDivider from "../../../components/MobileDivider";
|
||||
import SavingThrowsDetails from "../../../components/SavingThrowsDetails";
|
||||
import Senses from "../../../components/Senses";
|
||||
import SubsectionMobile from "../../../components/SubsectionMobile";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export default function MainMobile() {
|
||||
const abilities = useAbilities();
|
||||
@@ -35,18 +40,18 @@ export default function MainMobile() {
|
||||
const preferences = useSelector(rulesEngineSelectors.getCharacterPreferences);
|
||||
const savingThrowDiceAdjustments = useSelector(
|
||||
rulesEngineSelectors.getSavingThrowDiceAdjustments
|
||||
); // TODO: GFS move to mangers
|
||||
);
|
||||
const situationalBonusSavingThrowsLookup = useSelector(
|
||||
rulesEngineSelectors.getSituationalBonusSavingThrowsLookup
|
||||
); // TODO: GFS move to mangers
|
||||
);
|
||||
const passivePerception = useSelector(
|
||||
rulesEngineSelectors.getPassivePerception
|
||||
); // TODO: GFS move to mangers
|
||||
);
|
||||
const passiveInvestigation = useSelector(
|
||||
rulesEngineSelectors.getPassiveInvestigation
|
||||
); // TODO: GFS move to mangers
|
||||
const passiveInsight = useSelector(rulesEngineSelectors.getPassiveInsight); // TODO: GFS move to mangers
|
||||
const senses = useSelector(rulesEngineSelectors.getSenseInfo); // TODO: GFS move to mangers
|
||||
);
|
||||
const passiveInsight = useSelector(rulesEngineSelectors.getPassiveInsight);
|
||||
const senses = useSelector(rulesEngineSelectors.getSenseInfo);
|
||||
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
||||
const campaign = useSelector(rulesEngineSelectors.getCampaign);
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
@@ -56,6 +61,9 @@ export default function MainMobile() {
|
||||
characterRollContextSelectors.getCharacterRollContext
|
||||
);
|
||||
const gameLog = useSelector(appEnvSelectors.getGameLog);
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
||||
|
||||
const handleSensesManageShow = (): void => {
|
||||
paneHistoryStart(PaneComponentEnum.SENSE_MANAGE);
|
||||
@@ -90,6 +98,20 @@ export default function MainMobile() {
|
||||
paneHistoryStart(PaneComponentEnum.CAMPAIGN);
|
||||
};
|
||||
|
||||
const handleShortResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.SHORT_REST);
|
||||
};
|
||||
|
||||
const handleLongResetClick = (evt: React.MouseEvent): void => {
|
||||
evt.stopPropagation();
|
||||
evt.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
paneHistoryStart(PaneComponentEnum.LONG_REST);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubsectionMobile name="Main">
|
||||
<div
|
||||
@@ -97,7 +119,31 @@ export default function MainMobile() {
|
||||
theme.isDarkMode ? "ct-main-mobile--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
{campaign && (
|
||||
{isVttView && (
|
||||
<div className={styles.restButtonsGroup}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleShortResetClick}
|
||||
themed
|
||||
size="x-small"
|
||||
className={styles.restButton}
|
||||
>
|
||||
<ThemedShortRestSvg theme={theme} />
|
||||
<span>Short Rest</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleLongResetClick}
|
||||
themed
|
||||
size="x-small"
|
||||
className={styles.restButton}
|
||||
>
|
||||
<ThemedLongRestSvg theme={theme} />
|
||||
<span>Long Rest</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!isVttView && campaign && !isReadonly && (
|
||||
<CampaignSummary
|
||||
campaign={campaign}
|
||||
onCampaignShow={handleCampaignShow}
|
||||
@@ -111,7 +157,7 @@ export default function MainMobile() {
|
||||
/>
|
||||
)}
|
||||
<section className="ct-main-mobile__abilities">
|
||||
<h2 style={visuallyHidden}>Ability Scores</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Ability Scores</h2>
|
||||
{abilities.map((ability) => (
|
||||
<div className="ct-main-mobile__ability" key={ability.getStatKey()}>
|
||||
<AbilitySummary
|
||||
@@ -135,7 +181,7 @@ export default function MainMobile() {
|
||||
theme.isDarkMode ? "ct-main-mobile__saving-throws--dark-mode" : ""
|
||||
}`}
|
||||
>
|
||||
<h2 style={visuallyHidden}>Saving Throws</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Saving Throws</h2>
|
||||
<SavingThrowsSummary
|
||||
abilities={abilities}
|
||||
situationalBonusSavingThrowsLookup={
|
||||
@@ -156,17 +202,18 @@ export default function MainMobile() {
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
<MobileDivider
|
||||
label="Senses"
|
||||
label="Passive Senses"
|
||||
onClick={handleSensesManageShow}
|
||||
theme={theme}
|
||||
/>
|
||||
<h2 style={visuallyHidden}>Senses</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Passive Senses</h2>
|
||||
<Senses
|
||||
senses={senses}
|
||||
theme={theme}
|
||||
passiveInsight={Number(passiveInsight)}
|
||||
passiveInvestigation={Number(passiveInvestigation)}
|
||||
passivePerception={Number(passivePerception)}
|
||||
rowStyle="minimal"
|
||||
onClick={handleSensesManageShow}
|
||||
/>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import MobileDivider from "../../../components/MobileDivider";
|
||||
import SubsectionMobile from "../../../components/SubsectionMobile";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Notes from "../../Notes";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class NotesMobile extends React.PureComponent<Props> {
|
||||
@@ -23,7 +27,7 @@ class NotesMobile extends React.PureComponent<Props> {
|
||||
<SubsectionMobile className="ct-notes-mobile">
|
||||
<MobileDivider label="Notes" theme={theme} />
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Notes</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Notes</h2>
|
||||
<Notes />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { FC } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -35,7 +35,9 @@ export const ProficiencyGroupsMobile: FC<Props> = () => {
|
||||
theme={characterTheme}
|
||||
/>
|
||||
<section className="ct-proficiency-groups-mobile">
|
||||
<h2 style={visuallyHidden}>Proficiencies and Training</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>
|
||||
Proficiencies and Training
|
||||
</h2>
|
||||
<ProficiencyGroups
|
||||
proficiencyGroups={proficiencyGroups}
|
||||
onClick={handleManageShow}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -10,10 +9,11 @@ import {
|
||||
CharacterTheme,
|
||||
RuleData,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { IRollContext } from "@dndbeyond/dice";
|
||||
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import {
|
||||
@@ -33,7 +33,7 @@ interface Props {
|
||||
theme: CharacterTheme;
|
||||
diceEnabled: boolean;
|
||||
ruleData: RuleData;
|
||||
characterRollContext: IRollContext;
|
||||
characterRollContext: RollContext;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
}
|
||||
class SkillsBox extends React.PureComponent<Props> {
|
||||
@@ -88,7 +88,7 @@ class SkillsBox extends React.PureComponent<Props> {
|
||||
onClick={this.handleManageShow}
|
||||
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 { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -42,7 +42,7 @@ class SpellsMobile extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
/>
|
||||
<section className="ct-spells-mobile">
|
||||
<h2 style={visuallyHidden}>Spells</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Spells</h2>
|
||||
<Spells showNotes={false} />
|
||||
</section>
|
||||
<MobileDivider isEnd={true} theme={theme} />
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
import SubsectionTablet from "../../../components/SubsectionTablet";
|
||||
import TabletBox from "../../../components/TabletBox";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Actions from "../../Actions";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
isReadonly: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
@@ -25,7 +29,7 @@ class ActionsTablet extends React.PureComponent<Props> {
|
||||
<SubsectionTablet>
|
||||
<TabletBox theme={theme} header="Actions" className="ct-actions-tablet">
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Actions</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Actions</h2>
|
||||
<Actions />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { FC } from "react";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import SubsectionTablet from "../../../components/SubsectionTablet";
|
||||
import TabletBox from "../../../components/TabletBox";
|
||||
@@ -18,7 +18,7 @@ export const DescriptionTablet: FC = () => {
|
||||
theme={characterTheme}
|
||||
>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Background</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Background</h2>
|
||||
<Description theme={characterTheme} />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
+2
-2
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -43,7 +43,7 @@ class EquipmentTablet extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Inventory</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Inventory</h2>
|
||||
<Equipment />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -43,7 +43,7 @@ class ExtrasTablet extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Extras</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Extras</h2>
|
||||
<Extras />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
+7
-3
@@ -1,18 +1,22 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import SubsectionTablet from "../../../components/SubsectionTablet";
|
||||
import TabletBox from "../../../components/TabletBox";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Features from "../../Features";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class FeaturesTablet extends React.PureComponent<Props> {
|
||||
@@ -27,7 +31,7 @@ class FeaturesTablet extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Features and Traits</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Features and Traits</h2>
|
||||
<Features />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import {
|
||||
AbilitySummary,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useAbilities } from "~/hooks/useAbilities";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import { PaneComponentEnum } from "../../../../../../subApps/sheet/components/Sidebar/types";
|
||||
import {
|
||||
@@ -67,6 +68,9 @@ export default function MainTablet() {
|
||||
characterRollContextSelectors.getCharacterRollContext
|
||||
);
|
||||
const gameLog = useSelector(appEnvSelectors.getGameLog);
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
|
||||
const handleSensesManageShow = (): void => {
|
||||
paneHistoryStart(PaneComponentEnum.SENSE_MANAGE);
|
||||
};
|
||||
@@ -120,7 +124,7 @@ export default function MainTablet() {
|
||||
return (
|
||||
<SubsectionTablet name="Main">
|
||||
<div className="ct-main-tablet">
|
||||
{campaign && (
|
||||
{!isVttView && campaign && !isReadonly && (
|
||||
<CampaignSummary
|
||||
campaign={campaign}
|
||||
onCampaignShow={handleCampaignShow}
|
||||
@@ -134,7 +138,7 @@ export default function MainTablet() {
|
||||
/>
|
||||
)}
|
||||
<section className="ct-main-tablet__abilities">
|
||||
<h2 style={visuallyHidden}>Abilities</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Abilities</h2>
|
||||
{abilities.map((ability) => (
|
||||
<div className="ct-main-tablet__ability" key={ability.getStatKey()}>
|
||||
<AbilitySummary
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
|
||||
import SubsectionTablet from "../../../components/SubsectionTablet";
|
||||
import TabletBox from "../../../components/TabletBox";
|
||||
import { SheetAppState } from "../../../typings";
|
||||
import Notes from "../../Notes";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
|
||||
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
|
||||
interface Props extends DispatchProp<AnyAction> {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
class NotesTablet extends React.PureComponent<Props> {
|
||||
@@ -23,7 +27,7 @@ class NotesTablet extends React.PureComponent<Props> {
|
||||
<SubsectionTablet>
|
||||
<TabletBox header="Notes" className="ct-notes-tablet" theme={theme}>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Notes</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Notes</h2>
|
||||
<Notes />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import a11yStyles from "~/styles/accessibility.module.css";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { appEnvSelectors } from "../../../../Shared/selectors";
|
||||
@@ -43,7 +43,7 @@ class SpellsTablet extends React.PureComponent<Props> {
|
||||
theme={theme}
|
||||
>
|
||||
<section>
|
||||
<h2 style={visuallyHidden}>Spells</h2>
|
||||
<h2 className={a11yStyles.screenreaderOnly}>Spells</h2>
|
||||
<Spells />
|
||||
</section>
|
||||
</TabletBox>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user