New source found from dndbeyond.com
This commit is contained in:
@@ -11,18 +11,21 @@ import {
|
||||
HtmlSelectOptionGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Accordion } from "~/components/Accordion";
|
||||
import { CollapsibleContent } from "~/components/CollapsibleContent";
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { Select } from "~/components/Select";
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { useRuleData } from "~/hooks/useRuleData";
|
||||
import Collapsible from "~/tools/js/smartComponents/Collapsible";
|
||||
import { Select } from "~/tools/js/smartComponents/legacy";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
// TODO this needs to not extend builder contract and options typing needs to be fixed
|
||||
// TODO scss to styles.module.css
|
||||
interface Props extends Omit<BuilderChoiceContract, "options"> {
|
||||
choice: Choice;
|
||||
description?: string;
|
||||
descriptionIntro?: string;
|
||||
choiceInfo: ChoiceData;
|
||||
classId?: number | null;
|
||||
maxDescriptionLength?: number;
|
||||
@@ -42,6 +45,7 @@ interface Props extends Omit<BuilderChoiceContract, "options"> {
|
||||
export const DetailChoice: FC<Props> = ({
|
||||
choice,
|
||||
description,
|
||||
descriptionIntro,
|
||||
optionValue,
|
||||
parentChoiceId,
|
||||
maxDescriptionLength = 750,
|
||||
@@ -66,7 +70,7 @@ export const DetailChoice: FC<Props> = ({
|
||||
(ChoiceUtils.isInfinite(choice) && !ChoiceUtils.isOptionSelected(choice)) ||
|
||||
ChoiceUtils.isTodo(choice);
|
||||
|
||||
const handleChoiceChange = (value: string): void => {
|
||||
const handleChoiceChange = (value: string | number): void => {
|
||||
if (onChange && id !== null) {
|
||||
onChange(id, type, value, parentChoiceId);
|
||||
}
|
||||
@@ -92,44 +96,53 @@ export const DetailChoice: FC<Props> = ({
|
||||
choiceDescription = description || chosenOption["description"];
|
||||
}
|
||||
|
||||
const choiceDescriptionIntro = descriptionIntro ? (
|
||||
<p className={styles.descriptionIntro}>{descriptionIntro}</p>
|
||||
) : null;
|
||||
|
||||
let choiceNode: ReactNode;
|
||||
if (choiceDescription) {
|
||||
switch (type) {
|
||||
case Constants.BuilderChoiceTypeEnum.ENTITY_SPELL_OPTION:
|
||||
choiceNode = (
|
||||
<Collapsible layoutType={"minimal"} header="Spell Details">
|
||||
<Accordion variant="text" summary="Spell Details" size="xx-small">
|
||||
<HtmlContent
|
||||
className="detail-choice-description"
|
||||
html={choiceDescription}
|
||||
withoutTooltips
|
||||
/>
|
||||
</Collapsible>
|
||||
</Accordion>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (collapseDescription) {
|
||||
choiceNode = (
|
||||
<Collapsible layoutType={"minimal"} header="Show Details">
|
||||
<Accordion variant="text" summary="Show Details" size="xx-small">
|
||||
{choiceDescriptionIntro}
|
||||
<HtmlContent
|
||||
className="detail-choice-description"
|
||||
html={choiceDescription}
|
||||
withoutTooltips
|
||||
/>
|
||||
</Collapsible>
|
||||
</Accordion>
|
||||
);
|
||||
} else if (choiceDescription.length > maxDescriptionLength) {
|
||||
choiceNode = (
|
||||
<CollapsibleContent className="detail-choice-description">
|
||||
{choiceDescription}
|
||||
{choiceDescriptionIntro}
|
||||
<HtmlContent html={choiceDescription} />
|
||||
</CollapsibleContent>
|
||||
);
|
||||
} else {
|
||||
choiceNode = (
|
||||
<HtmlContent
|
||||
className="detail-choice-description"
|
||||
html={choiceDescription}
|
||||
withoutTooltips
|
||||
/>
|
||||
<>
|
||||
{choiceDescriptionIntro}
|
||||
<HtmlContent
|
||||
className="detail-choice-description"
|
||||
html={choiceDescription}
|
||||
withoutTooltips
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +158,8 @@ export const DetailChoice: FC<Props> = ({
|
||||
])}
|
||||
>
|
||||
<Select
|
||||
className="detail-choice-input"
|
||||
value={optionValue}
|
||||
placeholder={label ? `- ${label} -` : "- Choose an Option -"}
|
||||
options={ChoiceUtils.getSortedRenderOptions(
|
||||
options,
|
||||
classId,
|
||||
@@ -158,9 +172,10 @@ export const DetailChoice: FC<Props> = ({
|
||||
choiceInfo,
|
||||
optionValue
|
||||
)}
|
||||
value={optionValue}
|
||||
placeholder={label ? `- ${label} -` : "- Choose an Option -"}
|
||||
onChange={handleChoiceChange}
|
||||
searchThreshold={10}
|
||||
isHighlighted={hasTodo}
|
||||
name={`detail-choice-select-${choice.id}`}
|
||||
/>
|
||||
{choiceNode}
|
||||
</div>
|
||||
|
||||
@@ -1,158 +1,173 @@
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { DispatchProp, useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { TypeScriptUtils } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterUtils,
|
||||
CharacterTheme,
|
||||
ConfigUtils,
|
||||
rulesEngineSelectors,
|
||||
Constants,
|
||||
ApiException,
|
||||
CampaignDataContract,
|
||||
CampaignUtils,
|
||||
characterEnvSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { Dice, DiceNotifier, IRollContext } from "@dndbeyond/dice";
|
||||
import { DiceToolbar } from "@dndbeyond/dice-components";
|
||||
import { Platform } from "@dndbeyond/event-pipeline-lib/shared";
|
||||
import D20 from "@dndbeyond/fontawesome-cache/svgs/light/dice-d20.svg";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import { getMessageBroker } from "@dndbeyond/message-broker-lib";
|
||||
import {
|
||||
CustomDiceRoller,
|
||||
rollerImport,
|
||||
} from "@dndbeyond/pocket-dimension-dice/components/CustomDiceRoller";
|
||||
import "@dndbeyond/pocket-dimension-dice/components/CustomDiceRoller/index.css";
|
||||
import { Context as DiceWorkerContext } from "@dndbeyond/pocket-dimension-dice/context";
|
||||
import { instrumentDiceClear } from "@dndbeyond/pocket-dimension-dice/helpers/bi/instrumentDiceClear";
|
||||
import { instrumentDiceContainerClose } from "@dndbeyond/pocket-dimension-dice/helpers/bi/instrumentDiceContainerClose";
|
||||
import { instrumentDiceContainerOpen } from "@dndbeyond/pocket-dimension-dice/helpers/bi/instrumentDiceContainerOpen";
|
||||
import { instrumentDiceRoll } from "@dndbeyond/pocket-dimension-dice/helpers/bi/instrumentDiceRoll";
|
||||
import { useDiceSets } from "@dndbeyond/pocket-dimension-dice/hooks/useDiceSets";
|
||||
import { useUserDiceDataStore } from "@dndbeyond/pocket-dimension-dice/hooks/useUserDiceDataStore";
|
||||
import { AnchoredPopover } from "@dndbeyond/ttui/components/AnchoredPopover";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
import { useAuth } from "~/contexts/Authentication";
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
|
||||
import { appEnvActions } from "../../actions/appEnv";
|
||||
import {
|
||||
appEnvSelectors,
|
||||
characterRollContextSelectors,
|
||||
} from "../../selectors";
|
||||
import {
|
||||
DiceFeatureConfigurationState,
|
||||
SharedAppState,
|
||||
} from "../../stores/typings";
|
||||
import { AppLoggerUtils } from "../../utils";
|
||||
import { appEnvSelectors } from "../../selectors";
|
||||
import styles from "./DiceContainer.module.css";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
campaign: CampaignDataContract | null;
|
||||
canShow: boolean;
|
||||
theme: CharacterTheme;
|
||||
diceEnabled: boolean;
|
||||
diceFeatureConfiguration: DiceFeatureConfigurationState;
|
||||
characterRollContext: IRollContext;
|
||||
}
|
||||
const location = "characterSheet";
|
||||
|
||||
// <canvas id="dice-rolling-canvas" className={'dice-rolling-panel__container'} />
|
||||
// this is the canvas element that we will use to draw the dice
|
||||
// TODO: this is a temporary solution, we need to find a better way to do this
|
||||
// waiting for adventure team to provide a better solution
|
||||
const canvasElement = document.createElement("canvas");
|
||||
canvasElement.id = "dice-rolling-canvas";
|
||||
canvasElement.classList.add("dice-rolling-panel__container");
|
||||
|
||||
export default function DiceContainer({ canShow = true }) {
|
||||
export default function DiceContainer({ canShow = true, isVttView = false }) {
|
||||
const { nodeEnv, env } = useContext(DiceWorkerContext);
|
||||
const dispatch = useDispatch();
|
||||
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
const diceEnabled = useSelector(appEnvSelectors.getDiceEnabled);
|
||||
const diceFeatureConfiguration = useSelector(
|
||||
appEnvSelectors.getDiceFeatureConfiguration
|
||||
);
|
||||
const characterRollContext = useSelector(
|
||||
characterRollContextSelectors.getCharacterRollContext
|
||||
);
|
||||
const campaign = useSelector(rulesEngineSelectors.getCampaign);
|
||||
const userConfigApi = "/diceuserconfig/v1/get";
|
||||
const authUser = useAuth();
|
||||
let userId: string | null = null;
|
||||
if (authUser?.id) {
|
||||
userId = authUser.id;
|
||||
}
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption }] =
|
||||
useContext(GameLogContext);
|
||||
const { campaign, characterName } = useCharacterEngine();
|
||||
const user = useAuth();
|
||||
const [{ entityId }] = useContext(GameLogContext);
|
||||
const canvasContainer = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [isCustomDiceRollerOpen, setIsCustomDiceRollerOpen] = useState(false);
|
||||
const diceSets = useDiceSets(nodeEnv, env);
|
||||
const setId = useUserDiceDataStore(
|
||||
(state) => state.getUserDiceData(Number(user?.id))?.setId
|
||||
);
|
||||
const dmId = campaign
|
||||
? String(CampaignUtils.getDmUserId(campaign))
|
||||
: undefined;
|
||||
const isSheet =
|
||||
useSelector(characterEnvSelectors.getContext) ===
|
||||
Constants.AppContextTypeEnum.SHEET;
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasContainer.current?.contains(canvasElement)) {
|
||||
canvasContainer.current?.appendChild(canvasElement);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getMessageBroker().then((mb) => {
|
||||
if (mb && campaign && userId) {
|
||||
mb.gameId = isSheet ? CampaignUtils.getId(campaign).toString() : "0";
|
||||
mb.userId = userId;
|
||||
if (mb && user?.id) {
|
||||
if (campaign) {
|
||||
mb.gameId = CampaignUtils.getId(campaign).toString();
|
||||
} else {
|
||||
mb.gameId = undefined;
|
||||
}
|
||||
|
||||
mb.userId = user.id;
|
||||
}
|
||||
ConfigUtils.configureRulesEngine({ messageBroker: mb });
|
||||
if (diceFeatureConfiguration.enabled) {
|
||||
const rulesEngineConfig = ConfigUtils.getCurrentRulesEngineConfig();
|
||||
if (rulesEngineConfig.apiAdapter) {
|
||||
rulesEngineConfig.apiAdapter
|
||||
.get(`${diceFeatureConfiguration.apiEndpoint}${userConfigApi}`)
|
||||
.then((x) => {
|
||||
const diceCanvas = canvasElement;
|
||||
Dice.init({
|
||||
isDebug: false,
|
||||
assetBaseLocation: diceFeatureConfiguration.assetBaseLocation,
|
||||
trackingId: diceFeatureConfiguration.trackingId,
|
||||
sessionName: "Dice",
|
||||
notifier: new DiceNotifier(),
|
||||
canvas: diceCanvas || undefined,
|
||||
previewMode: false,
|
||||
diceUserConfig: ApiAdapterUtils.getResponseData(x) as any,
|
||||
})
|
||||
.then(() =>
|
||||
document.addEventListener("click", () => Dice.clear())
|
||||
)
|
||||
.catch((error: Error) => {
|
||||
AppLoggerUtils.logMessage(
|
||||
error.message,
|
||||
Constants.LogMessageType.WARNING
|
||||
);
|
||||
dispatch(
|
||||
appEnvActions.dataSet({
|
||||
diceEnabled: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((error: ApiException) => {
|
||||
AppLoggerUtils.logMessage(
|
||||
error.message,
|
||||
Constants.LogMessageType.WARNING
|
||||
);
|
||||
dispatch(
|
||||
appEnvActions.dataSet({
|
||||
diceEnabled: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Dice.analyticsInit(diceFeatureConfiguration.trackingId, "Dice");
|
||||
}
|
||||
});
|
||||
}, [diceFeatureConfiguration, dispatch]);
|
||||
}, [
|
||||
campaign,
|
||||
diceFeatureConfiguration.apiEndpoint,
|
||||
diceFeatureConfiguration.assetBaseLocation,
|
||||
diceFeatureConfiguration.enabled,
|
||||
diceFeatureConfiguration.trackingId,
|
||||
dispatch,
|
||||
isSheet,
|
||||
user?.id,
|
||||
]);
|
||||
|
||||
if (isVttView) return null;
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div className={"dice-rolling-panel"} ref={canvasContainer}>
|
||||
{canShow && diceEnabled && (
|
||||
<DiceToolbar
|
||||
rollContext={characterRollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions && isSheet
|
||||
? Object.values(messageTargetOptions?.entities).filter(
|
||||
TypeScriptUtils.isNotNullOrUndefined
|
||||
)
|
||||
: undefined
|
||||
<AnchoredPopover
|
||||
offset={16}
|
||||
open={isCustomDiceRollerOpen}
|
||||
allowedPlacements={["top-start"]}
|
||||
referenceComponent={
|
||||
<Button
|
||||
className={clsx([
|
||||
styles.button,
|
||||
isCustomDiceRollerOpen && styles.customDiceRollOpen,
|
||||
])}
|
||||
onClick={() => {
|
||||
setIsCustomDiceRollerOpen(!isCustomDiceRollerOpen);
|
||||
|
||||
// isCustomDiceRollerOpen is the previous state here
|
||||
if (isCustomDiceRollerOpen) {
|
||||
instrumentDiceContainerClose(
|
||||
user,
|
||||
Platform.Web,
|
||||
location,
|
||||
campaign?.id,
|
||||
undefined,
|
||||
env
|
||||
);
|
||||
} else {
|
||||
instrumentDiceContainerOpen(
|
||||
user,
|
||||
Platform.Web,
|
||||
location,
|
||||
campaign?.id,
|
||||
undefined,
|
||||
env
|
||||
);
|
||||
}
|
||||
}}
|
||||
themed
|
||||
onMouseEnter={rollerImport}
|
||||
>
|
||||
<D20 />
|
||||
</Button>
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
themeColor={theme.themeColor}
|
||||
userId={parseInt(userId || "0", 10)}
|
||||
/>
|
||||
floatingArrow
|
||||
floatingArrowClassName={styles.floatingArrow}
|
||||
>
|
||||
{diceSets && (
|
||||
<CustomDiceRoller
|
||||
rollCallback={() => setIsCustomDiceRollerOpen(false)}
|
||||
onClose={() => setIsCustomDiceRollerOpen(false)}
|
||||
diceSets={diceSets}
|
||||
gameId={campaign?.id}
|
||||
visible={isCustomDiceRollerOpen}
|
||||
setId={setId}
|
||||
entityType="character"
|
||||
entityId={entityId}
|
||||
name={characterName ?? undefined}
|
||||
rollInstrumentationCallback={(eventPayload) =>
|
||||
instrumentDiceRoll(
|
||||
user,
|
||||
eventPayload,
|
||||
Platform.Web,
|
||||
location,
|
||||
campaign?.id,
|
||||
undefined,
|
||||
env
|
||||
)
|
||||
}
|
||||
clearDiceCallback={() =>
|
||||
instrumentDiceClear(
|
||||
user,
|
||||
Platform.Web,
|
||||
location,
|
||||
campaign?.id,
|
||||
undefined,
|
||||
env
|
||||
)
|
||||
}
|
||||
dmId={dmId}
|
||||
/>
|
||||
)}
|
||||
</AnchoredPopover>
|
||||
)}
|
||||
{/* We want this one so it is sticking around,
|
||||
also this is where the canvas goes when we append */}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { StepType, TourContext, TourProvider } from "@reactour/tour";
|
||||
import { Dispatch, ReactNode, SetStateAction, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { checkContrast } from "../../utils/Color";
|
||||
import GuidedTourButton from "./GuidedTourButton";
|
||||
|
||||
interface GuidedTourProps {
|
||||
children?: ReactNode;
|
||||
steps?: StepType[];
|
||||
step?: number;
|
||||
setStep?: SetStateAction<any>;
|
||||
beforeClose?: (target: Element | null) => void;
|
||||
afterOpen?: (target: Element | null) => void;
|
||||
showOnFirstLoad?: boolean;
|
||||
cookieName?: string;
|
||||
}
|
||||
|
||||
export default ({
|
||||
children,
|
||||
step,
|
||||
setStep,
|
||||
steps = [],
|
||||
beforeClose,
|
||||
afterOpen,
|
||||
showOnFirstLoad,
|
||||
cookieName = "ddbGuidedTour",
|
||||
}: GuidedTourProps) => {
|
||||
const [defaultStep, setDefaultStep] = useState(0);
|
||||
const { isDarkMode, backgroundColor, themeColor } = useSelector(
|
||||
rulesEngineSelectors.getCharacterTheme
|
||||
);
|
||||
const bgColor = backgroundColor.slice(0, 7);
|
||||
const accentColor = checkContrast(backgroundColor, themeColor)
|
||||
? themeColor
|
||||
: "#C53131";
|
||||
|
||||
const handleBeforeClose = () => {
|
||||
const html = document.querySelector("html");
|
||||
if (html) html.style.overflowY = "visible";
|
||||
if (beforeClose) beforeClose(null);
|
||||
};
|
||||
|
||||
const handleAfterOpen = () => {
|
||||
const html = document.querySelector("html");
|
||||
if (html) html.style.overflowY = "hidden";
|
||||
if (afterOpen) afterOpen(null);
|
||||
};
|
||||
|
||||
const handleLoad = (setIsOpen: Dispatch<SetStateAction<boolean>>) => {
|
||||
if (showOnFirstLoad) {
|
||||
const viewedTour = localStorage.getItem(cookieName);
|
||||
|
||||
if (!viewedTour) {
|
||||
setIsOpen(true);
|
||||
localStorage.setItem(cookieName, "true");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
".reactour__popover": {
|
||||
backgroundColor: `${bgColor} !important`,
|
||||
color: isDarkMode
|
||||
? "rgba(255,255,255,0.9) !important"
|
||||
: "rgba(0,0,0,0.9) !important",
|
||||
borderRadius: 1,
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: isDarkMode
|
||||
? "rgba(255,255,255,0.3) !important"
|
||||
: "rgba(0,0,0,0.3) !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TourProvider
|
||||
steps={steps}
|
||||
currentStep={step || defaultStep}
|
||||
setCurrentStep={setStep || setDefaultStep}
|
||||
afterOpen={handleAfterOpen}
|
||||
beforeClose={handleBeforeClose}
|
||||
showDots={false}
|
||||
styles={{
|
||||
badge: (base) => ({
|
||||
...base,
|
||||
backgroundColor: accentColor,
|
||||
color: bgColor,
|
||||
fontWeight: 700,
|
||||
}),
|
||||
close: (base) => ({
|
||||
...base,
|
||||
color: isDarkMode ? "rgba(255,255,255,0.9)" : "rgba(0,0,0,0.9)",
|
||||
}),
|
||||
dot: (base, status) => ({
|
||||
...base,
|
||||
background: status?.current ? accentColor : "transparent",
|
||||
color: status?.current ? accentColor : "transparent",
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<TourContext.Consumer>
|
||||
{({ setIsOpen }) => (
|
||||
<div onLoad={() => handleLoad(setIsOpen)}>
|
||||
{children}
|
||||
<GuidedTourButton />
|
||||
</div>
|
||||
)}
|
||||
</TourContext.Consumer>
|
||||
</TourProvider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import QuestionMarkIcon from "@mui/icons-material/QuestionMark";
|
||||
import { Button } from "@mui/material";
|
||||
import { useTour } from "@reactour/tour";
|
||||
|
||||
export default () => {
|
||||
const { setIsOpen } = useTour();
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="ct-character-guided-tour"
|
||||
variant="contained"
|
||||
color="info"
|
||||
onClick={() => setIsOpen(true)}
|
||||
sx={{
|
||||
position: "fixed",
|
||||
zIndex: 9,
|
||||
p: 1,
|
||||
borderRadius: "100%",
|
||||
minWidth: 0,
|
||||
bottom: { xs: 100, md: 20 },
|
||||
right: 20,
|
||||
}}
|
||||
>
|
||||
<QuestionMarkIcon />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import GuidedTourButton from "./GuidedTourButton";
|
||||
|
||||
export default GuidedTourButton;
|
||||
export { GuidedTourButton };
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { useTour } from "@reactour/tour";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { checkContrast } from "../../../utils/Color";
|
||||
|
||||
export default ({ title, content, showClose = false }) => {
|
||||
const { themeColor, backgroundColor, isDarkMode } = useSelector(
|
||||
rulesEngineSelectors.getCharacterTheme
|
||||
);
|
||||
const { setIsOpen } = useTour();
|
||||
const accentColor = checkContrast(backgroundColor, themeColor)
|
||||
? themeColor
|
||||
: "#C53131";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4" component="p" mb={1}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="div"
|
||||
variant="body2"
|
||||
sx={{ a: { color: accentColor, textDecoration: "none" } }}
|
||||
>
|
||||
{content}
|
||||
</Typography>
|
||||
{showClose && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setIsOpen(false)}
|
||||
sx={{
|
||||
display: "inherit",
|
||||
my: 1,
|
||||
mx: "auto",
|
||||
fontSize: 12,
|
||||
color: isDarkMode
|
||||
? "rgba(255,255,255,0.9) !important"
|
||||
: "rgba(0,0,0,0.9) !important",
|
||||
borderColor: accentColor,
|
||||
}}
|
||||
>
|
||||
Start Playing!
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
CampaignUtils,
|
||||
serviceDataActions,
|
||||
Constants,
|
||||
Container,
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
GroupedMenuOption,
|
||||
serviceDataSelectors,
|
||||
PartyInfo,
|
||||
ItemPlan,
|
||||
ContainerLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
@@ -24,29 +26,18 @@ import { Popover } from "~/components/Popover";
|
||||
import { PopoverContent } from "~/components/PopoverContent";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { ButtonWithMenu } from "~/subApps/sheet/components/Sidebar/components/ButtonWithMenu";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import SimpleQuantity from "../../components/SimpleQuantity";
|
||||
import {
|
||||
RemoveButton,
|
||||
ThemeButton,
|
||||
ThemeButtonWithMenu,
|
||||
} from "../../components/common/Button";
|
||||
import { InventoryManagerContext } from "../../managers/InventoryManagerContext";
|
||||
import * as appEnvSelectors from "../../selectors/appEnv";
|
||||
import { SharedAppState } from "../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../utils";
|
||||
|
||||
interface LabelLookup {
|
||||
consume: string;
|
||||
remove: string;
|
||||
unequip: string;
|
||||
equip: string;
|
||||
attune: string;
|
||||
unattune: string;
|
||||
}
|
||||
|
||||
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> {
|
||||
item: Item;
|
||||
hasMaxAttunedItems: boolean;
|
||||
ruleData: RuleData;
|
||||
@@ -56,6 +47,10 @@ interface Props extends DispatchProp {
|
||||
partyInfo: PartyInfo | null;
|
||||
inventoryManager: InventoryManager;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
itemPlans: Array<ItemPlan>;
|
||||
containerLookup: ContainerLookup;
|
||||
isUserDM: boolean;
|
||||
onRemoveItem?: () => void;
|
||||
}
|
||||
interface State {
|
||||
quantity: number;
|
||||
@@ -98,8 +93,17 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
handleRemove = (): void => {
|
||||
const { paneHistoryStart, item, onPostRemoveNavigation, inventoryManager } =
|
||||
this.props;
|
||||
const {
|
||||
paneHistoryStart,
|
||||
item,
|
||||
onPostRemoveNavigation,
|
||||
inventoryManager,
|
||||
onRemoveItem,
|
||||
} = this.props;
|
||||
|
||||
if (onRemoveItem) {
|
||||
onRemoveItem();
|
||||
}
|
||||
|
||||
if (onPostRemoveNavigation) {
|
||||
paneHistoryStart(onPostRemoveNavigation);
|
||||
@@ -113,7 +117,13 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
handleRemoveInfusion = (): void => {
|
||||
const { dispatch, paneHistoryStart, item } = this.props;
|
||||
const {
|
||||
dispatch,
|
||||
paneHistoryStart,
|
||||
item,
|
||||
inventoryManager,
|
||||
containerLookup,
|
||||
} = this.props;
|
||||
|
||||
const infusion = ItemUtils.getInfusion(item);
|
||||
if (infusion) {
|
||||
@@ -137,7 +147,10 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
dispatch(
|
||||
serviceDataActions.infusionMappingDestroy(
|
||||
infusionId,
|
||||
InfusionUtils.getInventoryMappingId(infusion)
|
||||
InfusionUtils.getInventoryMappingId(infusion),
|
||||
inventoryManager.handleAcceptOnSuccess(
|
||||
ItemUtils.isShared(item, containerLookup)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -180,34 +193,30 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
inventoryManager.handleMove({ item, containerDefinitionKey });
|
||||
};
|
||||
|
||||
getLabels = (): LabelLookup => {
|
||||
return {
|
||||
consume: "Consume",
|
||||
remove: "Delete Item",
|
||||
unequip: "Unequip",
|
||||
equip: "Equip",
|
||||
attune: "Attune",
|
||||
unattune: "Unattune",
|
||||
};
|
||||
};
|
||||
|
||||
getContainerOptions = (): Array<GroupedMenuOption> => {
|
||||
const { containers, item, partyInfo } = this.props;
|
||||
const { containers, item } = this.props;
|
||||
|
||||
return ContainerUtils.getGroupedOptions(
|
||||
ItemUtils.getContainerDefinitionKey(item),
|
||||
containers,
|
||||
"Move To:",
|
||||
partyInfo
|
||||
? CampaignUtils.getSharingState(partyInfo)
|
||||
: Constants.PartyInventorySharingStateEnum.OFF
|
||||
"Move To:"
|
||||
);
|
||||
};
|
||||
|
||||
renderRemove = (): React.ReactNode => {
|
||||
const { item, containers, isReadonly, inventoryManager } = this.props;
|
||||
const {
|
||||
item,
|
||||
containers,
|
||||
isReadonly,
|
||||
inventoryManager,
|
||||
itemPlans,
|
||||
isUserDM,
|
||||
} = this.props;
|
||||
|
||||
const canRemoveItem = isUserDM
|
||||
? true
|
||||
: inventoryManager.canRemoveItem(item);
|
||||
|
||||
const canRemoveItem = inventoryManager.canRemoveItem(item);
|
||||
if (isReadonly || !canRemoveItem) {
|
||||
return null;
|
||||
}
|
||||
@@ -216,6 +225,7 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
|
||||
const name = ItemUtils.getName(item);
|
||||
const isContainer = ItemUtils.isContainer(item);
|
||||
const itemPlan = ItemUtils.getItemPlan(item, itemPlans);
|
||||
|
||||
if (isContainer) {
|
||||
const container = containers.find((container) => {
|
||||
@@ -230,13 +240,15 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
if (container) {
|
||||
if (infusion) {
|
||||
return (
|
||||
<RemoveButton
|
||||
<Button
|
||||
onClick={this.handleRemove}
|
||||
className="ct-item-detail__action"
|
||||
variant="outline"
|
||||
size="xx-small"
|
||||
themed
|
||||
data-testid="remove-infusion-button"
|
||||
>
|
||||
Remove Infusion
|
||||
</RemoveButton>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
const hasInfusions = ContainerUtils.hasInfusions(container);
|
||||
@@ -245,7 +257,7 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
<Popover
|
||||
trigger={
|
||||
<Button size="xx-small" variant="outline" themed>
|
||||
Delete
|
||||
Delete{itemPlan ? " Replicated Item" : ""}
|
||||
</Button>
|
||||
}
|
||||
position="bottom"
|
||||
@@ -262,43 +274,34 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
withCancel
|
||||
/>
|
||||
</Popover>
|
||||
// <RemoveButtonWithModal
|
||||
// className="ct-item-detail__action"
|
||||
// style={"outline"}
|
||||
// size="small"
|
||||
// modalTitle={`Delete ${name}?`}
|
||||
// modalContent={`Deleting the ${name} will also delete all of its${
|
||||
// hasInfusions ? " infusions and" : ""
|
||||
// } contents.`}
|
||||
// confirmCallback={() => {
|
||||
// this.handleRemove();
|
||||
// }}
|
||||
// data-testid="delete-item-button"
|
||||
// />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (infusion) {
|
||||
return (
|
||||
<RemoveButton
|
||||
<Button
|
||||
onClick={this.handleRemoveInfusion}
|
||||
className="ct-item-detail__action"
|
||||
variant="outline"
|
||||
size="xx-small"
|
||||
themed
|
||||
data-testid="remove-infusion-button"
|
||||
>
|
||||
Remove Infusion
|
||||
</RemoveButton>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RemoveButton
|
||||
<Button
|
||||
onClick={this.handleRemove}
|
||||
className="ct-item-detail__action"
|
||||
variant="outline"
|
||||
size="xx-small"
|
||||
themed
|
||||
data-testid="delete-item-button"
|
||||
>
|
||||
Delete
|
||||
</RemoveButton>
|
||||
Delete{itemPlan ? " Replicated Item" : ""}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -310,14 +313,11 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
containers,
|
||||
inventoryManager,
|
||||
} = this.props;
|
||||
|
||||
const labels = this.getLabels();
|
||||
const containerMoveOptions = this.getContainerOptions();
|
||||
const isEquipped = ItemUtils.isEquipped(item);
|
||||
const isAttuned = ItemUtils.isAttuned(item);
|
||||
const isStackable = ItemUtils.isStackable(item);
|
||||
const quantity = ItemUtils.getQuantity(item);
|
||||
const isContainer = ItemUtils.getDefinitionIsContainer(item);
|
||||
const parentContainer = ContainerUtils.getItemParentContainer(
|
||||
containers,
|
||||
ItemUtils.getContainerDefinitionKey(item)
|
||||
@@ -325,16 +325,20 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
if (!parentContainer) {
|
||||
return null;
|
||||
}
|
||||
const canEquip = inventoryManager.canEquipUnequipItem(item);
|
||||
const canAttune = inventoryManager.canAttuneUnattuneItem(item);
|
||||
const canModifyQuantity = inventoryManager.canModifyQuantity(item);
|
||||
const canMoveItem =
|
||||
inventoryManager.canMoveItem(item) &&
|
||||
!isContainer &&
|
||||
!isReadonly &&
|
||||
!!containers.length;
|
||||
const canShareItem = inventoryManager.canShareItem(item) && !isReadonly;
|
||||
const canClaimItem = inventoryManager.canClaimItem(item) && !isReadonly;
|
||||
const canEquip = inventoryManager.canEquipUnequipItem(item) && !isReadonly;
|
||||
const canAttune =
|
||||
inventoryManager.canAttuneUnattuneItem(item) && !isReadonly;
|
||||
const canModifyQuantity =
|
||||
inventoryManager.canModifyQuantity(item) && !isReadonly;
|
||||
const canMoveItem = inventoryManager.canMoveItem(item) && !isReadonly;
|
||||
const canShareContainer =
|
||||
inventoryManager.canShareContainer(item) && !isReadonly;
|
||||
const canClaimContainer =
|
||||
inventoryManager.canClaimContainer(item) && !isReadonly;
|
||||
const partyInventoryDefinitionKey =
|
||||
inventoryManager.getPartyEquipmentContainerDefinitionKey();
|
||||
const characterInventoryDefinitionKey =
|
||||
inventoryManager.getCharacterContainerDefinitionKey();
|
||||
|
||||
return (
|
||||
<div className="ct-item-detail__actions">
|
||||
@@ -342,80 +346,86 @@ class ItemDetailActions extends React.PureComponent<Props, State> {
|
||||
<SimpleQuantity
|
||||
quantity={quantity}
|
||||
onUpdate={this.handleQuantityUpdate}
|
||||
isReadonly={isReadonly || !canModifyQuantity}
|
||||
isReadonly={!canModifyQuantity}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canEquip && !isReadonly && (
|
||||
<ThemeButton
|
||||
{canEquip && (
|
||||
<Button
|
||||
onClick={isEquipped ? this.handleUnequip : this.handleEquip}
|
||||
style={isEquipped ? "" : "outline"}
|
||||
size="small"
|
||||
className="ct-item-detail__action"
|
||||
variant={isEquipped ? "solid" : "outline"}
|
||||
size="xx-small"
|
||||
themed
|
||||
data-testid="equip-item-button"
|
||||
>
|
||||
{isEquipped ? labels.unequip : labels.equip}
|
||||
</ThemeButton>
|
||||
{isEquipped ? "Unequip" : "Equip"}
|
||||
</Button>
|
||||
)}
|
||||
{canAttune && !isReadonly && (
|
||||
<ThemeButton
|
||||
{canAttune && (
|
||||
<Button
|
||||
onClick={isAttuned ? this.handleUnattune : this.handleAttune}
|
||||
style={isAttuned ? "" : "outline"}
|
||||
size="small"
|
||||
variant={isAttuned ? "solid" : "outline"}
|
||||
size="xx-small"
|
||||
themed
|
||||
disabled={hasMaxAttunedItems && !isAttuned}
|
||||
className="ct-item-detail__action"
|
||||
data-testid="attune-item-button"
|
||||
>
|
||||
{isAttuned ? labels.unattune : labels.attune}
|
||||
</ThemeButton>
|
||||
{isAttuned ? "Unattune" : "Attune"}
|
||||
</Button>
|
||||
)}
|
||||
{canShareItem && (
|
||||
<ThemeButton
|
||||
style="outline"
|
||||
onClick={() => {
|
||||
const definitionKey =
|
||||
inventoryManager.getPartyEquipmentContainerDefinitionKey();
|
||||
if (definitionKey) {
|
||||
this.handleMove(definitionKey);
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
className="ct-item-detail__action"
|
||||
{canShareContainer && partyInventoryDefinitionKey && (
|
||||
<ButtonWithMenu
|
||||
showSingleOption={true}
|
||||
placement="bottom-end"
|
||||
variant="outline"
|
||||
groupedOptions={[
|
||||
{
|
||||
label: "Move To:",
|
||||
options: [
|
||||
{
|
||||
label: "Party Inventory",
|
||||
value: partyInventoryDefinitionKey,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
onSelect={() => this.handleMove(partyInventoryDefinitionKey)}
|
||||
data-testid="move-item-button"
|
||||
>
|
||||
Move
|
||||
</ThemeButton>
|
||||
</ButtonWithMenu>
|
||||
)}
|
||||
{canClaimItem && (
|
||||
<ThemeButton
|
||||
style="outline"
|
||||
onClick={() => {
|
||||
const definitionKey =
|
||||
inventoryManager.getCharacterContainerDefinitionKey();
|
||||
if (definitionKey) {
|
||||
this.handleMove(definitionKey);
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
className="ct-item-detail__action"
|
||||
{canClaimContainer && characterInventoryDefinitionKey && (
|
||||
<ButtonWithMenu
|
||||
showSingleOption={true}
|
||||
placement="bottom-end"
|
||||
variant="outline"
|
||||
groupedOptions={[
|
||||
{
|
||||
label: "Move To:",
|
||||
options: [
|
||||
{
|
||||
label: "My Inventory",
|
||||
value: characterInventoryDefinitionKey,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
onSelect={() => this.handleMove(characterInventoryDefinitionKey)}
|
||||
data-testid="move-item-button"
|
||||
>
|
||||
Move
|
||||
</ThemeButton>
|
||||
</ButtonWithMenu>
|
||||
)}
|
||||
{canMoveItem && (
|
||||
<ThemeButtonWithMenu
|
||||
buttonStyle="outline"
|
||||
containerEl={
|
||||
document.querySelector(".ct-sidebar__portal") as HTMLElement
|
||||
}
|
||||
className="ct-item-detail__action"
|
||||
<ButtonWithMenu
|
||||
variant="outline"
|
||||
groupedOptions={containerMoveOptions}
|
||||
showSingleOption={true}
|
||||
onSelect={this.handleMove}
|
||||
>
|
||||
Move
|
||||
</ThemeButtonWithMenu>
|
||||
</ButtonWithMenu>
|
||||
)}
|
||||
{this.renderRemove()}
|
||||
</div>
|
||||
@@ -430,6 +440,9 @@ function mapStateToProps(state: SharedAppState) {
|
||||
hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
|
||||
containers: rulesEngineSelectors.getInventoryContainers(state),
|
||||
partyInfo: serviceDataSelectors.getPartyInfo(state),
|
||||
itemPlans: rulesEngineSelectors.getAvailableItemPlans(state),
|
||||
containerLookup: rulesEngineSelectors.getContainerLookup(state),
|
||||
isUserDM: appEnvSelectors.getIsUserDM(state),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -438,6 +451,7 @@ function ItemDetailActionsContainer(props) {
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
} = useSidebar();
|
||||
|
||||
return (
|
||||
<ItemDetailActions
|
||||
inventoryManager={inventoryManager}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Button,
|
||||
LoadingPlaceholder,
|
||||
Select,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import { Button, LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterPromise,
|
||||
ApiAdapterRequestConfig,
|
||||
@@ -30,9 +27,11 @@ import {
|
||||
StartingEquipmentSlotContract,
|
||||
TypeValueLookup,
|
||||
DefinitionUtils,
|
||||
HtmlSelectOption,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Link } from "~/components/Link";
|
||||
import { Select } from "~/components/Select";
|
||||
|
||||
// TODO need to remove this builder reference and replace with better heading
|
||||
import PageSubHeader from "../../../CharacterBuilder/components/PageSubHeader";
|
||||
@@ -42,6 +41,7 @@ import * as apiCreatorSelectors from "../../selectors/composite/apiCreator";
|
||||
import { SharedAppState } from "../../stores/typings";
|
||||
import { AppLoggerUtils, AppNotificationUtils } from "../../utils";
|
||||
import StartingEquipmentSlots from "./StartingEquipmentSlots";
|
||||
import styles from "./styles.module.css";
|
||||
import { StartingEquipmentRuleSlotSelection } from "./typings";
|
||||
|
||||
//duplicated from characterActions typingParts to avoid import issues
|
||||
@@ -51,6 +51,8 @@ interface StartingEquipmentAddRequestPayloadItem {
|
||||
entityId: number;
|
||||
entityTypeId: number;
|
||||
quantity: number;
|
||||
originEntityId: number | null;
|
||||
originEntityTypeId: number | null;
|
||||
}
|
||||
//duplicated from characterActions typingParts to avoid import issues
|
||||
interface StartingEquipmentAddRequestPayload {
|
||||
@@ -75,7 +77,9 @@ interface RuleChoiceGroupClearPayload {
|
||||
groupKey: RuleSlotChoiceKey;
|
||||
}
|
||||
|
||||
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> {
|
||||
isInitialView: boolean;
|
||||
onStartingEquipmentChoose?: () => void;
|
||||
startingClass: CharClass | null;
|
||||
@@ -527,6 +531,8 @@ class StartingEquipment extends React.PureComponent<Props, State> {
|
||||
entityId: item.id,
|
||||
entityTypeId: item.entityTypeId,
|
||||
quantity: rule.quantity ? rule.quantity : 1,
|
||||
originEntityId: null, //This was added for 2024 Artificer - they could be used for the starting equipment data origin
|
||||
originEntityTypeId: null, //This was added for 2024 Artificer - they could be used for the starting equipment data origin
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -673,9 +679,9 @@ class StartingEquipment extends React.PureComponent<Props, State> {
|
||||
diceValues = DiceUtils.getDiceValuesRange(wealthDice);
|
||||
}
|
||||
|
||||
const diceOptions = diceValues.map((value) => ({
|
||||
const diceOptions: Array<HtmlSelectOption> = diceValues.map((value) => ({
|
||||
value,
|
||||
label: value,
|
||||
label: String(value),
|
||||
}));
|
||||
|
||||
let totalGold = this.getStartingGoldTotal();
|
||||
@@ -691,10 +697,13 @@ class StartingEquipment extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="starting-equipment-gold-entry-input">
|
||||
<Select
|
||||
className={styles.select}
|
||||
placeholder={`${diceCount}d${diceValue}`}
|
||||
options={diceOptions}
|
||||
onChange={this.handleGoldRolledNumberChange}
|
||||
value={rolledGoldTotal}
|
||||
name="starting-gold-rolled"
|
||||
searchThreshold={null}
|
||||
/>
|
||||
</div>
|
||||
<div className="starting-equipment-gold-entry-multiplier">
|
||||
|
||||
+5
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { Checkbox, Select } from "@dndbeyond/character-components/es";
|
||||
import { Checkbox } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
BaseItemDefinitionContract,
|
||||
Constants,
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { Select } from "~/components/Select";
|
||||
|
||||
import { TypeScriptUtils } from "../../../utils";
|
||||
import { StartingEquipmentRuleSlotSelection } from "../typings";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
isMultiSlot: boolean;
|
||||
@@ -235,13 +237,14 @@ export default class StartingEquipmentRuleSlot extends React.PureComponent<Props
|
||||
contentNode = (
|
||||
<div className="starting-equipment-rule">
|
||||
<Select
|
||||
className={styles.select}
|
||||
options={groupedOptions}
|
||||
value={null}
|
||||
preventClickPropagating={true}
|
||||
onChange={this.handleRuleSelection.bind(
|
||||
this,
|
||||
ruleIdx
|
||||
)}
|
||||
name={`${name}-rule-${ruleIdx}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
FeatureManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { PaneInitFailureContent } from "~/subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
|
||||
import { PaneIdentifiersBlessing } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { Snippet } from "~/tools/js/smartComponents";
|
||||
|
||||
import { CharacterFeaturesManagerContext } from "../../../managers/CharacterFeaturesManagerContext";
|
||||
|
||||
interface Props {
|
||||
identifiers: PaneIdentifiersBlessing | null;
|
||||
}
|
||||
const BlessingPane: React.FC<Props> = ({ identifiers }) => {
|
||||
const { characterFeaturesManager } = useContext(
|
||||
CharacterFeaturesManagerContext
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
const [blessing, setBlessing] = useState<FeatureManager | null>(undefined!);
|
||||
|
||||
useEffect(() => {
|
||||
const id = identifiers?.id ?? null;
|
||||
|
||||
if (id) {
|
||||
const blessing = characterFeaturesManager.getBlessing(id);
|
||||
setBlessing(blessing);
|
||||
}
|
||||
}, [identifiers, characterFeaturesManager]);
|
||||
|
||||
if (blessing === null) {
|
||||
return <PaneInitFailureContent />;
|
||||
}
|
||||
|
||||
return blessing ? (
|
||||
<div className="ct-blessing-pane" key={blessing.getId()}>
|
||||
<Header>{blessing.getName()}</Header>
|
||||
<Snippet
|
||||
snippetData={snippetData}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
theme={theme}
|
||||
>
|
||||
{blessing.getDescription()}
|
||||
</Snippet>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default BlessingPane;
|
||||
@@ -1,4 +0,0 @@
|
||||
import BlessingPane from "./BlessingPane";
|
||||
|
||||
export default BlessingPane;
|
||||
export { BlessingPane };
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
BaseInventoryContract,
|
||||
@@ -49,7 +50,9 @@ import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../../utils";
|
||||
|
||||
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> {
|
||||
spells: Array<Spell>;
|
||||
spellCasterInfo: SpellCasterInfo;
|
||||
ruleData: RuleData;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
AnySimpleDataType,
|
||||
@@ -39,7 +40,9 @@ import SpellDetail from "../../../components/SpellDetail";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
spells: Array<Spell>;
|
||||
spellCasterInfo: SpellCasterInfo;
|
||||
ruleData: RuleData;
|
||||
@@ -203,13 +206,13 @@ class ClassSpellPane extends React.PureComponent<Props, State> {
|
||||
let expandedDataOriginRef = SpellUtils.getExpandedDataOriginRef(spell);
|
||||
if (expandedDataOriginRef !== null) {
|
||||
parentNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{parentNode} •{" "}
|
||||
{EntityUtils.getDataOriginRefName(
|
||||
expandedDataOriginRef,
|
||||
dataOriginRefData
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -1,6 +1,7 @@
|
||||
import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
@@ -21,7 +22,9 @@ import { SharedAppState } from "../../../stores/typings";
|
||||
import ConditionManagePaneSpecialConditions from "./ConditionManagePaneSpecialConditions";
|
||||
import ConditionManagePaneStandardConditions from "./ConditionManagePaneStandardConditions";
|
||||
|
||||
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> {
|
||||
activeConditions: Array<Condition>;
|
||||
ruleData: RuleData;
|
||||
isReadonly: boolean;
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ export default class ConditionManagePaneSpecialConditions extends React.PureComp
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{specialConditions.map((condition) => {
|
||||
let activeCondition = activeConditions.find(
|
||||
(activeCondition) =>
|
||||
@@ -53,7 +53,7 @@ export default class ConditionManagePaneSpecialConditions extends React.PureComp
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ export default class ConditionManagePaneStandardConditions extends React.PureCom
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{standardConditions.map((condition) => {
|
||||
let isActive = activeConditionIds.includes(
|
||||
ConditionUtils.getId(condition)
|
||||
@@ -43,7 +43,7 @@ export default class ConditionManagePaneStandardConditions extends React.PureCom
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Collapsible } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterPromise,
|
||||
ApiAdapterRequestConfig,
|
||||
ApiResponse,
|
||||
BaseItemDefinitionContract,
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
Container,
|
||||
@@ -36,6 +33,8 @@ import {
|
||||
CharacterCurrencyContract,
|
||||
FormatUtils,
|
||||
CoinManager,
|
||||
ItemPlanUtils,
|
||||
EntityUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { EditableName } from "~/components/EditableName";
|
||||
@@ -67,7 +66,9 @@ import { AppNotificationUtils, PaneIdentifierUtils } from "../../../utils";
|
||||
import ItemDetailActions from "../../ItemDetailActions";
|
||||
import { CurrencyErrorTypeEnum } from "../CurrencyPane/CurrencyPaneConstants";
|
||||
|
||||
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> {
|
||||
weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
|
||||
ruleData: RuleData;
|
||||
snippetData: SnippetData;
|
||||
@@ -75,7 +76,6 @@ interface Props extends DispatchProp {
|
||||
infusionChoiceLookup: InfusionChoiceLookup;
|
||||
identifiers: PaneIdentifiersContainer | null;
|
||||
isReadonly: boolean;
|
||||
proficiencyBonus: number;
|
||||
theme: CharacterTheme;
|
||||
containers: Array<Container>;
|
||||
globalModifiers: Array<Modifier>;
|
||||
@@ -343,6 +343,21 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
handleParentClick = (): void => {
|
||||
const { item } = this.state;
|
||||
const { paneHistoryPush } = this.props;
|
||||
if (item) {
|
||||
const itemPlan = item.getItemPlan();
|
||||
if (itemPlan) {
|
||||
const dataOrigin = ItemPlanUtils.getDataOrigin(itemPlan);
|
||||
let component = getDataOriginComponentInfo(dataOrigin);
|
||||
if (component.type !== PaneComponentEnum.ERROR_404) {
|
||||
paneHistoryPush(component.type, component.identifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { item, currentContainer, isCustomizeClosed } = this.state;
|
||||
const {
|
||||
@@ -352,7 +367,6 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
entityValueLookup,
|
||||
infusionChoiceLookup,
|
||||
isReadonly,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
containers,
|
||||
globalModifiers,
|
||||
@@ -360,7 +374,6 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
inventory,
|
||||
identifiers,
|
||||
partyInfo,
|
||||
inventoryManager,
|
||||
} = this.props;
|
||||
|
||||
if (currentContainer === null) {
|
||||
@@ -369,9 +382,12 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
|
||||
const shopOpenInitially = identifiers?.showAddItems ?? false;
|
||||
|
||||
const canCustomize = item
|
||||
? inventoryManager.canCustomizeItem(item.item)
|
||||
: false;
|
||||
let parentName: string | null = null;
|
||||
const itemPlan = item ? item.getItemPlan() : null;
|
||||
if (itemPlan) {
|
||||
const dataOrigin = ItemPlanUtils.getDataOrigin(itemPlan);
|
||||
parentName = EntityUtils.getDataOriginName(dataOrigin);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-container-pane">
|
||||
@@ -379,6 +395,8 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
preview={
|
||||
<Preview imageUrl={item ? item.getAvatarUrl() : undefined} />
|
||||
}
|
||||
parent={parentName}
|
||||
onClick={this.handleParentClick}
|
||||
>
|
||||
{item ? (
|
||||
<EditableName onClick={this.handleOpenCustomize}>
|
||||
@@ -411,12 +429,12 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
entityValueLookup={entityValueLookup}
|
||||
infusionChoiceLookup={infusionChoiceLookup}
|
||||
isReadonly={isReadonly}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
showActions={false}
|
||||
showCustomize={canCustomize}
|
||||
showCustomize={!isReadonly}
|
||||
container={currentContainer.container}
|
||||
isCustomizeClosed={isCustomizeClosed}
|
||||
onCustomizeClick={this.handleOpenCustomize}
|
||||
partyInfo={partyInfo}
|
||||
/>
|
||||
</Collapsible>
|
||||
)}
|
||||
@@ -424,7 +442,6 @@ class ContainerPane extends React.PureComponent<Props, State> {
|
||||
currentContainer={currentContainer.container}
|
||||
containers={containers}
|
||||
ruleData={ruleData}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
theme={theme}
|
||||
globalModifiers={globalModifiers}
|
||||
valueLookupByType={valueLookupByType}
|
||||
@@ -484,7 +501,6 @@ function mapStateToProps(state: SharedAppState) {
|
||||
snippetData: rulesEngineSelectors.getSnippetData(state),
|
||||
infusionChoiceLookup: rulesEngineSelectors.getInfusionChoiceLookup(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
containers: rulesEngineSelectors.getInventoryContainers(state),
|
||||
globalModifiers: rulesEngineSelectors.getValidGlobalModifiers(state),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { FeatureFlagContext } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
characterActions,
|
||||
CharacterCurrencyContract,
|
||||
@@ -15,12 +15,12 @@ import {
|
||||
serviceDataSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { SettingsButton } from "~/subApps/sheet/components/SettingsButton";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { PaneIdentifiersCurrencyContext } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import CurrencyCollapsible from "../../../../CharacterSheet/components/CurrencyCollapsible";
|
||||
import Lifestyle from "../../../../CharacterSheet/components/Lifestyle";
|
||||
import SettingsButton from "../../../../CharacterSheet/components/SettingsButton";
|
||||
import * as toastActions from "../../../actions/toastMessage/actions";
|
||||
import { CURRENCY_VALUE } from "../../../constants/App";
|
||||
import { CoinManagerContext } from "../../../managers/CoinManagerContext";
|
||||
@@ -29,7 +29,9 @@ import { SharedAppState } from "../../../stores/typings";
|
||||
import { SettingsContextsEnum } from "../SettingsPane/typings";
|
||||
import { CurrencyErrorTypeEnum } from "./CurrencyPaneConstants";
|
||||
|
||||
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> {
|
||||
lifestyle: CharacterLifestyleContract | null;
|
||||
coin: CharacterCurrencyContract;
|
||||
ruleData: RuleData;
|
||||
@@ -136,7 +138,7 @@ class CurrencyPane extends React.PureComponent<Props> {
|
||||
const { isReadonly, ruleData, lifestyle } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
@@ -159,7 +161,7 @@ class CurrencyPane extends React.PureComponent<Props> {
|
||||
lifestyle={lifestyle}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -190,86 +192,79 @@ class CurrencyPane extends React.PureComponent<Props> {
|
||||
);
|
||||
|
||||
return (
|
||||
<FeatureFlagContext.Consumer>
|
||||
{({ imsFlag }) => (
|
||||
<div className="ct-currency-pane">
|
||||
<Header
|
||||
callout={
|
||||
<SettingsButton
|
||||
context={SettingsContextsEnum.COIN}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
}
|
||||
<div className="ct-currency-pane">
|
||||
<Header
|
||||
callout={
|
||||
!isReadonly && (
|
||||
<SettingsButton context={SettingsContextsEnum.COIN} />
|
||||
)
|
||||
}
|
||||
>
|
||||
Manage Coin
|
||||
</Header>
|
||||
{partyDefinitionKey ? (
|
||||
<>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
Manage Coin
|
||||
</Header>
|
||||
{imsFlag &&
|
||||
coinManager.isSharingTurnedOnOrDeleteOnly() &&
|
||||
partyDefinitionKey ? (
|
||||
<React.Fragment>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
My Coin
|
||||
</div>
|
||||
{characterContainer && (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(characterContainer)
|
||||
)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(characterContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={characterContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
My Coin
|
||||
</div>
|
||||
{characterContainer && (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(characterContainer)
|
||||
)}
|
||||
<Lifestyle
|
||||
isReadonly={isReadonly}
|
||||
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
|
||||
lifestyle={lifestyle}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
Party Coin
|
||||
</div>
|
||||
{partyContainer && (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(partyContainer)
|
||||
)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(partyContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={partyContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(characterContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={characterContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
characterContainer && this.renderCharacterCoin(characterContainer)
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Lifestyle
|
||||
isReadonly={isReadonly}
|
||||
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
|
||||
lifestyle={lifestyle}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
Party Coin
|
||||
</div>
|
||||
{partyContainer && (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(partyContainer)
|
||||
)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(partyContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={partyContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
characterContainer && this.renderCharacterCoin(characterContainer)
|
||||
)}
|
||||
</FeatureFlagContext.Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,39 +292,72 @@ class CurrencyPane extends React.PureComponent<Props> {
|
||||
const containerDefinitionKeyContext =
|
||||
identifiers?.containerDefinitionKeyContext;
|
||||
return (
|
||||
<FeatureFlagContext.Consumer>
|
||||
{({ imsFlag }) => (
|
||||
<div className="ct-currency-pane">
|
||||
<Header
|
||||
callout={
|
||||
<SettingsButton
|
||||
context={SettingsContextsEnum.COIN}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
<div className="ct-currency-pane">
|
||||
<Header
|
||||
callout={
|
||||
!isReadonly && (
|
||||
<SettingsButton context={SettingsContextsEnum.COIN} />
|
||||
)
|
||||
}
|
||||
>
|
||||
Manage Coin
|
||||
</Header>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
My Coin
|
||||
</div>
|
||||
{characterContainers.map((characterContainer) =>
|
||||
ContainerUtils.getCoin(characterContainer) ? (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(characterContainer)
|
||||
)}
|
||||
key={ContainerUtils.getDefinitionKey(characterContainer)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(characterContainer)
|
||||
}
|
||||
>
|
||||
Manage Coin
|
||||
</Header>
|
||||
isReadonly={isReadonly}
|
||||
container={characterContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
<Lifestyle
|
||||
isReadonly={isReadonly}
|
||||
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
|
||||
lifestyle={lifestyle}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
{partyDefinitionKey && (
|
||||
<>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
My Coin
|
||||
Party Coin
|
||||
</div>
|
||||
{characterContainers.map((characterContainer) =>
|
||||
ContainerUtils.getCoin(characterContainer) ? (
|
||||
{partyContainers.map((partyContainer) =>
|
||||
ContainerUtils.getCoin(partyContainer) ? (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(characterContainer)
|
||||
ContainerUtils.getName(partyContainer)
|
||||
)}
|
||||
key={ContainerUtils.getDefinitionKey(characterContainer)}
|
||||
key={ContainerUtils.getDefinitionKey(partyContainer)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(characterContainer)
|
||||
ContainerUtils.getDefinitionKey(partyContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={characterContainer}
|
||||
container={partyContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
@@ -338,51 +366,9 @@ class CurrencyPane extends React.PureComponent<Props> {
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
<Lifestyle
|
||||
isReadonly={isReadonly}
|
||||
handleLifestyleUpdate={this.handleLifestyleUpdate.bind(this)}
|
||||
lifestyle={lifestyle}
|
||||
ruleData={ruleData}
|
||||
/>
|
||||
{imsFlag &&
|
||||
coinManager.isSharingTurnedOnOrDeleteOnly() &&
|
||||
partyDefinitionKey && (
|
||||
<>
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={2}
|
||||
className="ct-currency-pane__subheader"
|
||||
>
|
||||
Party Coin
|
||||
</div>
|
||||
{partyContainers.map((partyContainer) =>
|
||||
ContainerUtils.getCoin(partyContainer) ? (
|
||||
<CurrencyCollapsible
|
||||
heading={this.deriveCoinLabel(
|
||||
ContainerUtils.getName(partyContainer)
|
||||
)}
|
||||
key={ContainerUtils.getDefinitionKey(partyContainer)}
|
||||
initiallyCollapsed={
|
||||
containerDefinitionKeyContext !==
|
||||
ContainerUtils.getDefinitionKey(partyContainer)
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
container={partyContainer}
|
||||
handleCurrencyChangeError={this.handleCurrencyChangeError.bind(
|
||||
this
|
||||
)}
|
||||
handleCurrencyAdjust={this.handleCurrencyAdjust.bind(
|
||||
this
|
||||
)}
|
||||
handleAmountSet={this.handleAmountSet.bind(this)}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</FeatureFlagContext.Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -1,6 +1,3 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
import { Button } from "@dndbeyond/character-components/es";
|
||||
@@ -9,6 +6,9 @@ import {
|
||||
CoinManager,
|
||||
Constants,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import RemoveIcon from "@dndbeyond/fontawesome-cache/svgs/sharp-regular/minus.svg";
|
||||
import AddIcon from "@dndbeyond/fontawesome-cache/svgs/sharp-regular/plus.svg";
|
||||
import CloseIcon from "@dndbeyond/fontawesome-cache/svgs/sharp-regular/xmark.svg";
|
||||
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ThemeButton } from "../../../../components/common/Button";
|
||||
import { CURRENCY_VALUE } from "../../../../constants/App";
|
||||
import { CoinManagerContext } from "../../../../managers/CoinManagerContext";
|
||||
import CurrencyPaneAdjusterType from "../CurrencyPaneAdjusterType";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
onAdjust?: (
|
||||
@@ -184,7 +185,7 @@ class CurrencyPaneAdjuster extends React.PureComponent<
|
||||
<div className="ct-currency-pane__adjuster-action ct-currency-pane__adjuster-action--positive">
|
||||
<Button size="medium" onClick={this.handleAdd}>
|
||||
<span className="ct-currency-pane__adjuster-actions-button-content">
|
||||
<AddIcon sx={{ width: "13px", height: "13px" }} />
|
||||
<AddIcon className={styles.icon} />
|
||||
Add
|
||||
</span>
|
||||
</Button>
|
||||
@@ -192,7 +193,7 @@ class CurrencyPaneAdjuster extends React.PureComponent<
|
||||
<div className="ct-currency-pane__adjuster-action ct-currency-pane__adjuster-action--negative">
|
||||
<Button size="medium" onClick={this.handleRemove}>
|
||||
<span className="ct-currency-pane__adjuster-actions-button-content">
|
||||
<RemoveIcon sx={{ width: "13px", height: "13px" }} />
|
||||
<RemoveIcon className={styles.icon} />
|
||||
Remove
|
||||
</span>
|
||||
</Button>
|
||||
@@ -204,7 +205,7 @@ class CurrencyPaneAdjuster extends React.PureComponent<
|
||||
onClick={this.handleReset}
|
||||
>
|
||||
<span className="ct-currency-pane__adjuster-actions-button-content">
|
||||
<CloseIcon sx={{ width: "13px", height: "13px" }} />
|
||||
<CloseIcon className={styles.icon} />
|
||||
Clear
|
||||
</span>
|
||||
</ThemeButton>
|
||||
|
||||
+3
-3
@@ -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 {
|
||||
propertyKey: string;
|
||||
className: string;
|
||||
}
|
||||
export default class CurrencyPaneEditor extends React.PureComponent<Props> {
|
||||
export default class CurrencyPaneEditor extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class CurrencyPaneEditorValue extends React.PureComponent {
|
||||
export default class CurrencyPaneEditorValue extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-currency-pane__editor-value">
|
||||
|
||||
+3
-2
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
import { Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
import CurrencyPaneEditor from "../CurrencyPaneEditor";
|
||||
@@ -62,7 +62,8 @@ export default class CurrencyPaneSelectEditor extends React.PureComponent<
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
isReadonly={isReadonly}
|
||||
disabled={isReadonly}
|
||||
name={`currency-select-${propertyKey}`}
|
||||
/>
|
||||
</CurrencyPaneEditorValue>
|
||||
</CurrencyPaneEditor>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Collapsible } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -40,7 +41,9 @@ type EditCustomContractProperties = Omit<
|
||||
"id" | "entityTypeId"
|
||||
>;
|
||||
|
||||
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> {
|
||||
actions: Array<Action>;
|
||||
entityValueLookup: EntityValueLookup;
|
||||
abilityLookup: AbilityLookup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
Action,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
RuleData,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
@@ -22,7 +23,9 @@ import { SharedAppState } from "../../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../../utils";
|
||||
import CustomActionsPaneSummary from "./CustomActionsPaneSummary";
|
||||
|
||||
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> {
|
||||
actions: Array<Action>;
|
||||
ruleData: RuleData;
|
||||
paneContext: PaneInfo;
|
||||
@@ -124,17 +127,16 @@ class CustomActionsPane extends React.PureComponent<Props> {
|
||||
<Heading>Add new Actions</Heading>
|
||||
<Select
|
||||
options={customActionOptions}
|
||||
resetAfterChoice={true}
|
||||
onChange={this.handleActionAdd}
|
||||
value={null}
|
||||
name="custom-action-add"
|
||||
resetAfterChoice
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-custom-actions-pane">
|
||||
<Header>Manage Custom Actions</Header>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -32,7 +33,9 @@ import { RemoveButton } from "../../../components/common/Button";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
customSkills: Array<Skill>;
|
||||
ruleData: RuleData;
|
||||
identifiers: PaneIdentifiersCustomSkill | null;
|
||||
@@ -198,8 +201,6 @@ class CustomSkillPane extends React.PureComponent<Props, State> {
|
||||
const proficiencyLevel = SkillUtils.getProficiencyLevel(skill);
|
||||
const name = SkillUtils.getName(skill);
|
||||
|
||||
const infoItemProps = { role: "listitem", inline: true };
|
||||
|
||||
return (
|
||||
<div className="ct-custom-skill-pane" key={skill.id}>
|
||||
<Header parent="Skills" onClick={this.handleSkillsManageShow}>
|
||||
@@ -222,9 +223,7 @@ class CustomSkillPane extends React.PureComponent<Props, State> {
|
||||
{this.renderCustomize()}
|
||||
{notes && (
|
||||
<div className="ct-custom-skill-pane__properties" role="list">
|
||||
<InfoItem label="Notes" {...infoItemProps}>
|
||||
{notes}
|
||||
</InfoItem>
|
||||
<InfoItem label="Notes">{notes}</InfoItem>
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
|
||||
+7
-3
@@ -2,6 +2,7 @@ import axios, { Canceler } from "axios";
|
||||
import { uniq } from "lodash";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -27,7 +28,10 @@ interface BackdropGroups {
|
||||
label: string;
|
||||
backdrops: Array<CharacterBackdropContract>;
|
||||
}
|
||||
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> {
|
||||
decorationInfo: DecorationInfo;
|
||||
startingClass: CharClass | null;
|
||||
}
|
||||
@@ -171,7 +175,7 @@ class BackdropManager extends React.PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{startingClassBackdrop && (
|
||||
<div className="ct-decoration-manager__group">
|
||||
<Heading>Default</Heading>
|
||||
@@ -195,7 +199,7 @@ class BackdropManager extends React.PureComponent<Props, State> {
|
||||
{backdropGroups.map((backdropGroup) =>
|
||||
this.renderItems(backdropGroup.label, backdropGroup.backdrops)
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import BackdropManager from "./BackdropManager";
|
||||
|
||||
export default BackdropManager;
|
||||
export { BackdropManager };
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { FC, MouseEvent, PropsWithChildren } from "react";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
onClick?: (decorationKey: string) => void;
|
||||
label: string;
|
||||
isActive: boolean;
|
||||
isReadonly: boolean;
|
||||
decorationKey: string;
|
||||
}
|
||||
const CurrentDecorationItem: React.FC<Props> = ({
|
||||
const CurrentDecorationItem: FC<Props> = ({
|
||||
label,
|
||||
onClick,
|
||||
isActive,
|
||||
@@ -18,7 +18,7 @@ const CurrentDecorationItem: React.FC<Props> = ({
|
||||
return (
|
||||
<div
|
||||
className="ct-decorate-pane__grid-item"
|
||||
onClick={(evt: React.MouseEvent) => {
|
||||
onClick={(evt: MouseEvent) => {
|
||||
evt.stopPropagation();
|
||||
if (onClick) {
|
||||
onClick(decorationKey);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import CurrentDecorationItem from "./CurrentDecorationItem";
|
||||
|
||||
export default CurrentDecorationItem;
|
||||
export { CurrentDecorationItem };
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
AbilitySummary,
|
||||
@@ -14,13 +14,12 @@ import {
|
||||
DecorationUtils,
|
||||
rulesEngineSelectors,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { Dice } from "@dndbeyond/dice";
|
||||
|
||||
import { useAbilities } from "~/hooks/useAbilities";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
import { appEnvActions } from "../../../actions/appEnv";
|
||||
import CtaPreferenceManager from "../../../components/CtaPreferenceManager";
|
||||
import { AttributesManagerContext } from "../../../managers/AttributesManagerContext";
|
||||
import { characterRollContextSelectors } from "../../../selectors";
|
||||
@@ -48,7 +47,6 @@ export default function DecoratePane() {
|
||||
const dispatch = useDispatch();
|
||||
const decorationInfo = useSelector(rulesEngineSelectors.getDecorationInfo);
|
||||
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
||||
const diceEnabled = useSelector(appEnvSelectors.getDiceEnabled);
|
||||
const preferences = useSelector(rulesEngineSelectors.getCharacterPreferences);
|
||||
const characterRollContext = useSelector(
|
||||
characterRollContextSelectors.getCharacterRollContext
|
||||
@@ -63,22 +61,6 @@ export default function DecoratePane() {
|
||||
);
|
||||
};
|
||||
|
||||
//TODO this is repeated a few times now.. reusable thing? hook?
|
||||
const handleDiceToggle = (): void => {
|
||||
const newDiceEnabledSetting: boolean = !diceEnabled;
|
||||
|
||||
try {
|
||||
localStorage.setItem("dice-enabled", newDiceEnabledSetting.toString());
|
||||
Dice.setEnabled(newDiceEnabledSetting);
|
||||
} catch (e) {}
|
||||
|
||||
dispatch(
|
||||
appEnvActions.dataSet({
|
||||
diceEnabled: newDiceEnabledSetting,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleClick = (type: SHOP_KEY): void => {
|
||||
setCurrentShop(type);
|
||||
};
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, MouseEvent, PropsWithChildren } from "react";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PropsWithChildren {
|
||||
avatarId: number | null;
|
||||
avatarName?: string | null;
|
||||
isSelected?: boolean;
|
||||
@@ -9,8 +9,8 @@ interface Props {
|
||||
className?: string;
|
||||
innerClassName?: string;
|
||||
}
|
||||
export default class DecorationPreviewItem extends React.PureComponent<Props> {
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
export default class DecorationPreviewItem extends PureComponent<Props> {
|
||||
handleClick = (evt: MouseEvent): void => {
|
||||
const { onSelected, avatarId } = this.props;
|
||||
|
||||
if (onSelected) {
|
||||
|
||||
+6
-3
@@ -2,6 +2,7 @@ import axios, { Canceler } from "axios";
|
||||
import { uniq } from "lodash";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -26,7 +27,9 @@ interface FrameGroupInfo {
|
||||
frames: Array<CharacterPortraitFrameContract>;
|
||||
}
|
||||
|
||||
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> {
|
||||
decorationInfo: DecorationInfo;
|
||||
}
|
||||
interface State {
|
||||
@@ -167,7 +170,7 @@ class FrameManager extends React.PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-decoration-manager__group">
|
||||
<Heading>Default</Heading>
|
||||
<div className="ct-decoration-manager__list">
|
||||
@@ -184,7 +187,7 @@ class FrameManager extends React.PureComponent<Props, State> {
|
||||
{frameGroups.map((frameGroup) =>
|
||||
this.renderFrames(frameGroup.label, frameGroup.frames)
|
||||
)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+6
-3
@@ -1,6 +1,7 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
ApiRequests,
|
||||
@@ -25,7 +26,9 @@ import LoadingPlaceholder from "~/tools/js/smartComponents/LoadingPlaceholder";
|
||||
|
||||
import DecorationPreviewItem from "../DecorationPreviewItem";
|
||||
|
||||
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> {
|
||||
decorationInfo: DecorationInfo;
|
||||
species: Race | null;
|
||||
handleSelectPortrait?: (portrait: CharacterPortraitContract) => void;
|
||||
@@ -383,7 +386,7 @@ class PortraitManager extends React.PureComponent<Props, State> {
|
||||
const otherPortraitLabel: string =
|
||||
speciesPortraits.length > 0 ? "Other Portraits" : "Portraits";
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{this.renderUploadedPortrait()}
|
||||
{this.renderPortraitUploader()}
|
||||
{species &&
|
||||
@@ -393,7 +396,7 @@ class PortraitManager extends React.PureComponent<Props, State> {
|
||||
speciesPortraits
|
||||
)}
|
||||
{this.renderPortraits(otherPortraitLabel, otherPortraits)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import { uniq } from "lodash";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
AbilitySummary,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { useAbilities } from "~/hooks/useAbilities";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
import { AttributesManagerContext } from "~/tools/js/Shared/managers/AttributesManagerContext";
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import clsx from "clsx";
|
||||
import { sortBy } from "lodash";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
DamageTypeIcon,
|
||||
DataOriginName,
|
||||
Select,
|
||||
ComponentConstants,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -29,8 +30,10 @@ import {
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import formStyles from "~/subApps/builder/styles/form.module.css";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { getDataOriginComponentInfo } from "~/subApps/sheet/components/Sidebar/helpers/paneUtils";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
@@ -38,6 +41,7 @@ import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
import * as appEnvSelectors from "../../../selectors/appEnv";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import DefenseManagePaneCustomItem from "./DefenseManagePaneCustomItem";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
const DEFENSE_ADJUSTMENT_TYPE_OPTION = {
|
||||
RESISTANCE: 1,
|
||||
@@ -54,7 +58,9 @@ interface AdjustmentGroupInfo {
|
||||
adjustments: Array<AdjustmentGroupEntry>;
|
||||
}
|
||||
|
||||
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> {
|
||||
resistances: Array<DefenseAdjustment>;
|
||||
immunities: Array<DefenseAdjustment>;
|
||||
vulnerabilities: Array<DefenseAdjustment>;
|
||||
@@ -324,7 +330,7 @@ class DefenseManagePane extends React.PureComponent<Props, State> {
|
||||
{ label: "Vulnerabilities", adjustments: [] },
|
||||
];
|
||||
customDefenseAdjustments.forEach((defenseAdjustment, newDefIdx) => {
|
||||
const { type, adjustmentId, source } = defenseAdjustment;
|
||||
const { type, adjustmentId } = defenseAdjustment;
|
||||
|
||||
let groupIdx: number | null = null;
|
||||
let definition:
|
||||
@@ -484,26 +490,30 @@ class DefenseManagePane extends React.PureComponent<Props, State> {
|
||||
header="Customize"
|
||||
className="ct-defense-manage-pane__custom"
|
||||
>
|
||||
<div className="ct-defense-manage-pane__custom-fields">
|
||||
<div className="dct-defense-manage-pane__custom-field">
|
||||
<label className={clsx([formStyles.label, styles.label])}>
|
||||
Defense Type
|
||||
</label>
|
||||
<Select
|
||||
options={adjustmentTypeOptions}
|
||||
onChange={this.handleChangeAdjustmentType}
|
||||
value={adjustmentType}
|
||||
name="defense-adjustment-type"
|
||||
/>
|
||||
{adjustmentType !== null && (
|
||||
<>
|
||||
<label className={clsx([formStyles.label, styles.label])}>
|
||||
Defense Sub-Type
|
||||
</label>
|
||||
<Select
|
||||
options={adjustmentTypeOptions}
|
||||
onChange={this.handleChangeAdjustmentType}
|
||||
value={adjustmentType}
|
||||
options={sortBy(adjustmentSubTypeOptions, "label")}
|
||||
onChange={this.handleChangeAdjustmentSubType}
|
||||
resetAfterChoice={true}
|
||||
placeholder={subTypePlaceholder}
|
||||
value={null}
|
||||
name="defense-adjustment-sub-type"
|
||||
/>
|
||||
</div>
|
||||
<div className="ct-defense-manage-pane__custom-field">
|
||||
{adjustmentType !== null && (
|
||||
<Select
|
||||
options={sortBy(adjustmentSubTypeOptions, "label")}
|
||||
onChange={this.handleChangeAdjustmentSubType}
|
||||
resetAfterChoice={true}
|
||||
placeholder={subTypePlaceholder}
|
||||
value={null}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="ct-defense-manage-pane__custom-items">
|
||||
{this.renderCustomDefenseAdjustmentList()}
|
||||
</div>
|
||||
@@ -523,11 +533,11 @@ class DefenseManagePane extends React.PureComponent<Props, State> {
|
||||
const { resistances, vulnerabilities, immunities } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{this.renderDamageAdjustmentGroup("Resistances", resistances)}
|
||||
{this.renderDamageAdjustmentGroup("Immunities", immunities)}
|
||||
{this.renderDamageAdjustmentGroup("Vulnerabilities", vulnerabilities)}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
AlignmentContract,
|
||||
@@ -23,7 +24,9 @@ import DescriptionPaneNumberEditor from "./DescriptionPaneNumberEditor";
|
||||
import DescriptionPaneSelectEditor from "./DescriptionPaneSelectEditor";
|
||||
import DescriptionPaneTextEditor from "./DescriptionPaneTextEditor";
|
||||
|
||||
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> {
|
||||
height: string | null;
|
||||
weight: number | null;
|
||||
size: SizeContract | null;
|
||||
@@ -123,7 +126,7 @@ class DescriptionPane extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<DescriptionPaneEntry>
|
||||
<DescriptionPaneSelectEditor
|
||||
label="Alignment"
|
||||
@@ -157,7 +160,7 @@ class DescriptionPane extends React.PureComponent<Props> {
|
||||
{lifestyleDescriptionNode}
|
||||
</DescriptionPaneEntryContent>
|
||||
</DescriptionPaneEntry>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -165,7 +168,7 @@ class DescriptionPane extends React.PureComponent<Props> {
|
||||
const { height, weight, skin, eyes, hair, age, gender } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<DescriptionPaneEntry>
|
||||
<DescriptionPaneTextEditor
|
||||
label="Hair"
|
||||
@@ -227,7 +230,7 @@ class DescriptionPane extends React.PureComponent<Props> {
|
||||
maxLength={128}
|
||||
/>
|
||||
</DescriptionPaneEntry>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -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 {
|
||||
propertyKey: string;
|
||||
className: string;
|
||||
}
|
||||
export default class DescriptionPaneEditor extends React.PureComponent<Props> {
|
||||
export default class DescriptionPaneEditor extends PureComponent<Props> {
|
||||
render() {
|
||||
const { propertyKey, className } = this.props;
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class DescriptionPaneEditorLabel extends React.PureComponent {
|
||||
export default class DescriptionPaneEditorLabel extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-description-pane__editor-label">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class DescriptionPaneEditorValue extends React.PureComponent {
|
||||
export default class DescriptionPaneEditorValue extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-description-pane__editor-value">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class DescriptionPaneEntry extends React.PureComponent {
|
||||
export default class DescriptionPaneEntry extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-description-pane__entry">{this.props.children}</div>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PureComponent, PropsWithChildren } from "react";
|
||||
|
||||
export default class DescriptionPaneEntryContent extends React.PureComponent {
|
||||
export default class DescriptionPaneEntryContent extends PureComponent<PropsWithChildren> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-description-pane__entry-content">
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
import { Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
|
||||
import DescriptionPaneEditor from "../DescriptionPaneEditor";
|
||||
import DescriptionPaneEditorLabel from "../DescriptionPaneEditorLabel";
|
||||
import DescriptionPaneEditorValue from "../DescriptionPaneEditorValue";
|
||||
@@ -56,11 +57,11 @@ export default class DescriptionPaneSelectEditor extends React.PureComponent<
|
||||
<DescriptionPaneEditorLabel>{label}</DescriptionPaneEditorLabel>
|
||||
<DescriptionPaneEditorValue>
|
||||
<Select
|
||||
className="ct-description-pane__editor-input"
|
||||
placeholder="--"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
name={propertyKey}
|
||||
/>
|
||||
</DescriptionPaneEditorValue>
|
||||
</DescriptionPaneEditor>
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { SettingsButton } from "~/subApps/sheet/components/SettingsButton";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
import SettingsButton from "../../../../CharacterSheet/components/SettingsButton";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { SettingsContextsEnum } from "../SettingsPane/typings";
|
||||
@@ -30,7 +30,6 @@ interface Props {
|
||||
}
|
||||
class EncumbrancePane extends React.PureComponent<Props> {
|
||||
renderWeight = (label, weight): React.ReactNode => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<div className="ct-encumbrance-pane__weight">
|
||||
<span className="ct-encumbrance-pane__weight-label">{label}:</span>
|
||||
@@ -47,14 +46,14 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
// item weight should be the weight of items without coins
|
||||
// and coin weight should be the weight of all coins in all equipped containers
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Heading>Current Weight Distribution</Heading>
|
||||
<div className="ct-encumbrance-pane__weights">
|
||||
{this.renderWeight("Items", itemWeight)}
|
||||
{!preferences.ignoreCoinWeight &&
|
||||
this.renderWeight("Coins", coinWeight)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,10 +71,9 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
<div className="ct-encumbrance-pane">
|
||||
<Header
|
||||
callout={
|
||||
<SettingsButton
|
||||
context={SettingsContextsEnum.ENCUMBRANCE}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
!isReadonly && (
|
||||
<SettingsButton context={SettingsContextsEnum.ENCUMBRANCE} />
|
||||
)
|
||||
}
|
||||
>
|
||||
Encumbrance
|
||||
@@ -84,7 +82,7 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
<div className="ct-encumbrance-pane__info">
|
||||
{preferences.encumbranceType ===
|
||||
Constants.PreferenceEncumbranceTypeEnum.NONE && (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-encumbrance-pane__weights">
|
||||
{this.renderWeight("Carrying Capacity", carryCapacity)}
|
||||
{this.renderWeight("Push, Drag, or Lift", pushDragLiftWeight)}
|
||||
@@ -94,11 +92,11 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
<p>
|
||||
Encumbrance rules are not currently applied to this character.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
{preferences.encumbranceType ===
|
||||
Constants.PreferenceEncumbranceTypeEnum.ENCUMBRANCE && (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-encumbrance-pane__weights">
|
||||
{this.renderWeight("Carrying Capacity", carryCapacity)}
|
||||
{this.renderWeight("Push, Drag, or Lift", pushDragLiftWeight)}
|
||||
@@ -131,11 +129,11 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
amount it can push, drag, or lift. For a Tiny creature, halve
|
||||
these weights.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
{preferences.encumbranceType ===
|
||||
Constants.PreferenceEncumbranceTypeEnum.VARIANT && (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-encumbrance-pane__weights">
|
||||
{this.renderWeight("Carrying Capacity", carryCapacity)}
|
||||
{this.renderWeight("Push, Drag, or Lift", pushDragLiftWeight)}
|
||||
@@ -167,7 +165,7 @@ class EncumbrancePane extends React.PureComponent<Props> {
|
||||
disadvantage on ability checks, attack rolls, and saving throws
|
||||
that use Strength, Dexterity, or Constitution.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+84
-76
@@ -14,15 +14,14 @@ import {
|
||||
rulesEngineSelectors,
|
||||
BaseItemDefinitionContract,
|
||||
RuleData,
|
||||
CampaignUtils,
|
||||
CharacterTheme,
|
||||
Container,
|
||||
ContainerUtils,
|
||||
InventoryManager,
|
||||
Constants,
|
||||
serviceDataSelectors,
|
||||
PartyInfo,
|
||||
ItemManager,
|
||||
ContainerManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
@@ -33,23 +32,24 @@ import { Popover } from "~/components/Popover";
|
||||
import { PopoverContent } from "~/components/PopoverContent";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { ButtonWithMenu } from "~/subApps/sheet/components/Sidebar/components/ButtonWithMenu";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { CustomItemCreator } from "../../../components/CustomItemCreator";
|
||||
import EquipmentShop from "../../../components/EquipmentShop";
|
||||
import ItemDetail from "../../../components/ItemDetail";
|
||||
import { ItemSlotManager } from "../../../components/ItemSlotManager";
|
||||
import { ThemeButtonWithMenu } from "../../../components/common/Button";
|
||||
import { InventoryManagerContext } from "../../../managers/InventoryManagerContext";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
loadAvailableItems: (
|
||||
additionalConfig?: Partial<ApiAdapterRequestConfig>
|
||||
) => ApiAdapterPromise<ApiResponse<Array<BaseItemDefinitionContract>>>;
|
||||
ruleData: RuleData;
|
||||
proficiencyBonus: number;
|
||||
containers: Array<Container>;
|
||||
theme: CharacterTheme;
|
||||
partyInfo: PartyInfo | null;
|
||||
@@ -59,79 +59,82 @@ interface Props {
|
||||
|
||||
class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
renderInventory = (inventory: Array<ItemManager>): React.ReactNode => {
|
||||
const {
|
||||
theme,
|
||||
ruleData,
|
||||
proficiencyBonus,
|
||||
inventoryManager,
|
||||
partyInfo,
|
||||
containers,
|
||||
} = this.props;
|
||||
const { theme, ruleData, inventoryManager, partyInfo, containers } =
|
||||
this.props;
|
||||
|
||||
return inventory.map((item) => {
|
||||
return inventory.map((item, i) => {
|
||||
const isContainer = item.isContainer();
|
||||
const container = inventoryManager.getContainer(
|
||||
item.generateContainerDefinitionKey()
|
||||
);
|
||||
|
||||
// to ensure key is unique even if two items are the same, append index
|
||||
// the index helps in re-renders when items are added/removed/moved around
|
||||
const itemKey = item.getUniqueKey() + i;
|
||||
const canMove = inventoryManager.canMoveItem(item.item);
|
||||
const canDelete = inventoryManager.canRemoveItem(item.item);
|
||||
|
||||
const calloutButton: React.ReactNode = isContainer ? (
|
||||
<Popover
|
||||
trigger={
|
||||
<Button size="xx-small" variant="outline" themed>
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
position="bottomRight"
|
||||
data-testid="remove-container-button"
|
||||
maxWidth={250}
|
||||
>
|
||||
<PopoverContent
|
||||
title={`Remove ${item.getName()}?`}
|
||||
content={`Removing the ${item.getName()} will also remove all of its ${
|
||||
container && container.hasInfusions() ? "infusions and " : " "
|
||||
} contents.`}
|
||||
confirmText="Delete"
|
||||
onConfirm={() => item.handleRemove()}
|
||||
withCancel
|
||||
/>
|
||||
</Popover>
|
||||
) : canMove ? (
|
||||
<ThemeButtonWithMenu
|
||||
showSingleOption={true}
|
||||
containerEl={
|
||||
document.querySelector(".ct-sidebar__portal") as HTMLElement
|
||||
}
|
||||
groupedOptions={ContainerUtils.getGroupedOptions(
|
||||
item.getContainerDefinitionKey(),
|
||||
containers,
|
||||
"Move To:",
|
||||
partyInfo
|
||||
? CampaignUtils.getSharingState(partyInfo)
|
||||
: Constants.PartyInventorySharingStateEnum.OFF
|
||||
)}
|
||||
buttonStyle="outline"
|
||||
onSelect={(containerDefinitionKey) =>
|
||||
item.handleMove({ containerDefinitionKey })
|
||||
}
|
||||
>
|
||||
Move
|
||||
</ThemeButtonWithMenu>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => item.handleRemove()}
|
||||
size="xx-small"
|
||||
variant="outline"
|
||||
themed
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
//if the item is a container - render the delete button withPopover only if it "canDelete"
|
||||
//if the item is not a container - render the move button if it "canMove" otherwise render the delete button if it "canDelete"
|
||||
//if neither - render null;
|
||||
let calloutButton: React.ReactNode = null;
|
||||
if (isContainer && canDelete) {
|
||||
calloutButton = (
|
||||
<Popover
|
||||
trigger={
|
||||
<Button size="xx-small" variant="outline" themed>
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
position="bottomRight"
|
||||
data-testid="remove-container-button"
|
||||
maxWidth={250}
|
||||
>
|
||||
<PopoverContent
|
||||
title={`Remove ${item.getName()}?`}
|
||||
content={`Removing the ${item.getName()} will also remove all of its ${
|
||||
container && container.hasInfusions() ? "infusions and " : " "
|
||||
} contents.`}
|
||||
confirmText="Delete"
|
||||
onConfirm={() => item.handleRemove()}
|
||||
withCancel
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
} else if (!isContainer && canMove) {
|
||||
calloutButton = (
|
||||
<ButtonWithMenu
|
||||
showSingleOption={true}
|
||||
placement="bottom-end"
|
||||
variant="outline"
|
||||
groupedOptions={ContainerUtils.getGroupedOptions(
|
||||
item.getContainerDefinitionKey(),
|
||||
containers,
|
||||
"Move To:"
|
||||
)}
|
||||
onSelect={(containerDefinitionKey) =>
|
||||
item.handleMove({ containerDefinitionKey })
|
||||
}
|
||||
>
|
||||
Move
|
||||
</ButtonWithMenu>
|
||||
);
|
||||
} else if (!isContainer && canDelete) {
|
||||
calloutButton = (
|
||||
<Button
|
||||
onClick={() => item.handleRemove()}
|
||||
size="xx-small"
|
||||
variant="outline"
|
||||
themed
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
key={item.getUniqueKey()}
|
||||
key={itemKey}
|
||||
className={`ct-equipment-manage-pane__item${
|
||||
item.isContainer()
|
||||
? " ct-equipment-manage-pane__item--is-container"
|
||||
@@ -158,7 +161,6 @@ class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
}
|
||||
}}
|
||||
useTooltip={false}
|
||||
showEmptySlot={!isContainer}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -186,22 +188,24 @@ class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
ruleData={ruleData}
|
||||
showCustomize={false}
|
||||
showImage={false}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
partyInfo={partyInfo}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
renderContainers = (): React.ReactNode => {
|
||||
const { theme, inventoryManager } = this.props;
|
||||
renderContainers = (containers: ContainerManager[]): React.ReactNode => {
|
||||
return containers.map((container) => {
|
||||
const isEquipmentContainer =
|
||||
container.isCharacterContainer() || container.isPartyContainer();
|
||||
|
||||
return inventoryManager.getAllContainers().map((container) => {
|
||||
const containerInventory = container.getInventoryItems({
|
||||
includeContainer: true,
|
||||
}).items;
|
||||
|
||||
const inventoryLength = containerInventory.length;
|
||||
const inventoryLength =
|
||||
containerInventory.length - (isEquipmentContainer ? 0 : 1); //don't count the container item itself if its an item and not the character/party container
|
||||
let isEmpty: boolean = inventoryLength === 0;
|
||||
|
||||
let headerNode: React.ReactNode = (
|
||||
@@ -239,11 +243,11 @@ class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
ruleData,
|
||||
proficiencyBonus,
|
||||
containers,
|
||||
theme,
|
||||
partyInfo,
|
||||
paneHistoryPush,
|
||||
inventoryManager,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -255,13 +259,18 @@ class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
containers={containers}
|
||||
theme={theme}
|
||||
ruleData={ruleData}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
<CustomItemCreator containers={containers} />
|
||||
</Collapsible>
|
||||
|
||||
{/*container collapsibles*/}
|
||||
{this.renderContainers()}
|
||||
|
||||
<Heading className={styles.containerHeading}>My Inventory</Heading>
|
||||
{this.renderContainers(inventoryManager.getCharacterContainers())}
|
||||
{inventoryManager.getPartyContainers().length > 0 && (
|
||||
<Heading className={styles.containerHeading}>Party Inventory</Heading>
|
||||
)}
|
||||
{this.renderContainers(inventoryManager.getPartyContainers())}
|
||||
|
||||
<div className="ct-equipment-manage-pane__links">
|
||||
<div className="ct-equipment-manage-pane__link">
|
||||
@@ -299,7 +308,6 @@ class EquipmentManagePane extends React.PureComponent<Props> {
|
||||
function mapStateToProps(state: SharedAppState) {
|
||||
return {
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
|
||||
containers: rulesEngineSelectors.getInventoryContainers(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
partyInfo: serviceDataSelectors.getPartyInfo(state),
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FC, HTMLAttributes, useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
LoadingPlaceholder,
|
||||
ThemedExportSvg,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import PdfSvg from "@dndbeyond/fontawesome-cache/svgs/light/file-pdf.svg";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { usePdfExport } from "~/hooks/usePdfExport";
|
||||
@@ -44,9 +42,10 @@ export const ExportPdfPane: FC<Props> = ({ ...props }) => {
|
||||
|
||||
return (
|
||||
<div className="ct-export-pdf-pane" {...props}>
|
||||
<div className="ct-export-pdf-pane__splash-icon">
|
||||
<ThemedExportSvg theme={characterTheme} />
|
||||
</div>
|
||||
<PdfSvg
|
||||
className="ddbc-svg ddbc-svg--themed ct-export-pdf-pane__splash-icon"
|
||||
style={{ fill: characterTheme.themeColor }}
|
||||
/>
|
||||
<Header>PDF Generated</Header>
|
||||
<div className="ct-export-pdf-pane__url">
|
||||
<input
|
||||
|
||||
@@ -3,13 +3,13 @@ import axios, { Canceler } from "axios";
|
||||
import { orderBy } from "lodash";
|
||||
import React, { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
Collapsible,
|
||||
LoadingPlaceholder,
|
||||
Select,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { Reference } from "~/components/Reference";
|
||||
import { Select } from "~/components/Select";
|
||||
import { useExtras } from "~/hooks/useExtras";
|
||||
import { CreatureBlock } from "~/subApps/sheet/components/CreatureBlock";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
} from "../../../utils";
|
||||
import ExtraManagePaneAddListing from "./ExtraManagePaneAddListing";
|
||||
import ExtraManagePaneCurrentListing from "./ExtraManagePaneCurrentListing";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
//TODO: this comes from ExtrasManager types
|
||||
interface ShoppeState {
|
||||
@@ -83,7 +85,9 @@ enum DataRetrievalStatusEnum {
|
||||
LOADED = "LOADED",
|
||||
}
|
||||
|
||||
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> {
|
||||
ruleData: RuleData;
|
||||
pageSize: number;
|
||||
creatureGroupRulesLookup: CreatureGroupRulesLookup;
|
||||
@@ -410,10 +414,13 @@ class ExtraManagePane extends React.PureComponent<Props, State> {
|
||||
<div className="ct-extra-manage-pane__filters-challenge-fields">
|
||||
<div className="ct-extra-manage-pane__filters-challenge-field">
|
||||
<Select
|
||||
className={styles.levelSelect}
|
||||
value={filterChallengeMin}
|
||||
options={challengeOptions}
|
||||
onChange={this.handleChallengeMinChange}
|
||||
placeholder="--"
|
||||
name="min-challenge"
|
||||
searchThreshold={null}
|
||||
/>
|
||||
</div>
|
||||
<div className="ct-extra-manage-pane__filters-challenge-sep">
|
||||
@@ -421,10 +428,13 @@ class ExtraManagePane extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="ct-extra-manage-pane__filters-challenge-field">
|
||||
<Select
|
||||
className={styles.levelSelect}
|
||||
value={filterChallengeMax}
|
||||
options={challengeOptions}
|
||||
onChange={this.handleChallengeMaxChange}
|
||||
placeholder="--"
|
||||
name="max-challenge"
|
||||
searchThreshold={null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -449,6 +459,7 @@ class ExtraManagePane extends React.PureComponent<Props, State> {
|
||||
<ExtraManagePaneAddListing
|
||||
{...listingComponentPropMappingFunc(extra)}
|
||||
isReadonly={isReadonly}
|
||||
key={listingComponentPropMappingFunc(extra).key}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -575,6 +586,7 @@ class ExtraManagePane extends React.PureComponent<Props, State> {
|
||||
onChange={this.handleSelectedGroupChange}
|
||||
placeholder="-- Choose a Category --"
|
||||
disabled={dataStatus === DataRetrievalStatusEnum.LOADING}
|
||||
name="extra-category"
|
||||
/>
|
||||
</div>
|
||||
{selectedGroup && (
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
import { MarketplaceCta } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
Creature,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
VehicleManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { MarketplaceCta } from "~/components/MarketplaceCta";
|
||||
import { Reference } from "~/components/Reference";
|
||||
import { CreatureBlock } from "~/subApps/sheet/components/CreatureBlock";
|
||||
|
||||
|
||||
+7
-7
@@ -1,22 +1,22 @@
|
||||
import React from "react";
|
||||
import { ReactNode, FC, PropsWithChildren } from "react";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleHeaderContent,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
|
||||
interface Props {
|
||||
headerCallout: React.ReactNode;
|
||||
heading: React.ReactNode;
|
||||
metaItems: Array<React.ReactNode>;
|
||||
interface Props extends PropsWithChildren {
|
||||
headerCallout: ReactNode;
|
||||
heading: ReactNode;
|
||||
metaItems: Array<ReactNode>;
|
||||
}
|
||||
export const ExtraManagePaneListingExtra: React.FC<Props> = ({
|
||||
export const ExtraManagePaneListingExtra: FC<Props> = ({
|
||||
headerCallout,
|
||||
heading,
|
||||
metaItems,
|
||||
children,
|
||||
}) => {
|
||||
let header: React.ReactNode = (
|
||||
let header: ReactNode = (
|
||||
<CollapsibleHeaderContent
|
||||
heading={heading}
|
||||
metaItems={metaItems}
|
||||
|
||||
+47
-31
@@ -3,11 +3,11 @@ import { orderBy } from "lodash";
|
||||
import * as React from "react";
|
||||
import { useContext } from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
CreaturePreview,
|
||||
ItemPreview,
|
||||
MarketplaceCta,
|
||||
LoadingPlaceholder,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -42,10 +42,12 @@ import {
|
||||
ClassUtils,
|
||||
BaseCharClass,
|
||||
ContainerManager,
|
||||
ContainerLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { InventoryManager } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { MarketplaceCta } from "~/components/MarketplaceCta";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
@@ -72,7 +74,9 @@ enum UiViewEnum {
|
||||
|
||||
type SimpleOnClickHandler = () => void;
|
||||
|
||||
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> {
|
||||
infusionChoices: Array<InfusionChoice>;
|
||||
inventory: Array<Item>;
|
||||
inventoryLookup: InventoryLookup;
|
||||
@@ -87,7 +91,6 @@ interface Props extends DispatchProp {
|
||||
|
||||
identifiers: PaneIdentifiersInfusionChoice;
|
||||
isReadonly: boolean;
|
||||
proficiencyBonus: number;
|
||||
maximumInfusions?: number;
|
||||
currentInfusions?: number;
|
||||
characterId: number;
|
||||
@@ -95,6 +98,8 @@ interface Props extends DispatchProp {
|
||||
inventoryManager: InventoryManager;
|
||||
classes: Array<BaseCharClass>;
|
||||
paneHistoryPush: PaneInfo["paneHistoryPush"];
|
||||
containerLookup: ContainerLookup;
|
||||
partyInventoryLookup: InventoryLookup;
|
||||
}
|
||||
interface StateData {
|
||||
infusionChoice: InfusionChoice | null;
|
||||
@@ -408,7 +413,13 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
handleInfusionRemove = (): void => {
|
||||
const { dispatch, inventoryLookup } = this.props;
|
||||
const {
|
||||
dispatch,
|
||||
inventoryLookup,
|
||||
partyInventoryLookup,
|
||||
inventoryManager,
|
||||
containerLookup,
|
||||
} = this.props;
|
||||
const { infusionChoice } = this.state;
|
||||
|
||||
if (infusionChoice === null) {
|
||||
@@ -424,7 +435,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
|
||||
const inventoryMappingId = InfusionUtils.getInventoryMappingId(infusion);
|
||||
const item = HelperUtils.lookupDataOrFallback(
|
||||
inventoryLookup,
|
||||
{ ...inventoryLookup, ...partyInventoryLookup },
|
||||
inventoryMappingId
|
||||
);
|
||||
|
||||
@@ -434,7 +445,12 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
dispatch(
|
||||
serviceDataActions.infusionMappingDestroy(
|
||||
infusionId,
|
||||
InfusionUtils.getInventoryMappingId(infusion)
|
||||
InfusionUtils.getInventoryMappingId(infusion),
|
||||
item
|
||||
? inventoryManager.handleAcceptOnSuccess(
|
||||
ItemUtils.isShared(item, containerLookup)
|
||||
)
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -624,8 +640,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
|
||||
renderUiChooseItem = (): React.ReactNode => {
|
||||
const { infusionChoice } = this.state;
|
||||
const { inventory, ruleData, proficiencyBonus, theme, inventoryManager } =
|
||||
this.props;
|
||||
const { inventory, ruleData, theme, inventoryManager } = this.props;
|
||||
|
||||
if (infusionChoice === null) {
|
||||
return null;
|
||||
@@ -653,7 +668,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header
|
||||
parent={this.renderInfusionChoiceName()}
|
||||
onClick={this.handleChangeUiView.bind(this, UiViewEnum.REVIEW)}
|
||||
@@ -683,7 +698,6 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
infusionChoice={infusionChoice}
|
||||
ruleData={ruleData}
|
||||
onItemSelected={this.handleChooseExistingItem}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -703,7 +717,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -742,7 +756,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header
|
||||
parent={this.renderInfusionChoiceName()}
|
||||
onClick={this.handleChangeUiView.bind(this, UiViewEnum.REVIEW)}
|
||||
@@ -784,7 +798,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -870,10 +884,10 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
Infusion: {InfusionUtils.getName(simulatedInfusion)}
|
||||
{extraNameNode}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -958,11 +972,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
} else if (infusionType === Constants.InfusionTypeEnum.REPLICATE) {
|
||||
itemHeaderNode = "Item to be replicated";
|
||||
itemContentNode = (
|
||||
<React.Fragment>
|
||||
{KnownInfusionUtils.getItemName(knownInfusion)}
|
||||
</React.Fragment>
|
||||
);
|
||||
itemContentNode = <>{KnownInfusionUtils.getItemName(knownInfusion)}</>;
|
||||
}
|
||||
|
||||
const modifierData = InfusionUtils.getModifierData(simulatedInfusion);
|
||||
@@ -1014,7 +1024,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>{this.renderInfusionChoiceName()}</Header>
|
||||
{currentInfusions >= maximumInfusions && (
|
||||
<ThemeButton
|
||||
@@ -1047,7 +1057,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
)}
|
||||
{!isReadonly && this.renderActions()}
|
||||
{this.renderDescription()}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1087,8 +1097,14 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
|
||||
renderInfusedUi = (): React.ReactNode => {
|
||||
const { infusionChoice } = this.state;
|
||||
const { ruleData, creatureLookup, inventoryLookup, isReadonly, theme } =
|
||||
this.props;
|
||||
const {
|
||||
ruleData,
|
||||
creatureLookup,
|
||||
inventoryLookup,
|
||||
isReadonly,
|
||||
theme,
|
||||
partyInventoryLookup,
|
||||
} = this.props;
|
||||
|
||||
if (infusionChoice === null) {
|
||||
return null;
|
||||
@@ -1101,7 +1117,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
|
||||
const inventoryMappingId = InfusionUtils.getInventoryMappingId(infusion);
|
||||
const item = HelperUtils.lookupDataOrFallback(
|
||||
inventoryLookup,
|
||||
{ ...inventoryLookup, ...partyInventoryLookup },
|
||||
inventoryMappingId
|
||||
);
|
||||
const creatureMappingId = InfusionUtils.getCreatureMappingId(infusion);
|
||||
@@ -1114,7 +1130,7 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>{this.renderInfusionChoiceName()}</Header>
|
||||
{item !== null && (
|
||||
<InfusionChoicePaneStep
|
||||
@@ -1138,17 +1154,17 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
{this.renderDescription()}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingUi = (): React.ReactNode => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>{this.renderInfusionChoiceName()}</Header>
|
||||
<LoadingPlaceholder />
|
||||
{this.renderDescription()}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1195,7 +1211,6 @@ class InfusionChoicePane extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<div className="ct-infusion-choice-pane__marketplace-cta">
|
||||
<MarketplaceCta
|
||||
showImage={false}
|
||||
sourceName={sourceName}
|
||||
description="To unlock this infusion, check out the Marketplace to view purchase options."
|
||||
/>
|
||||
@@ -1258,10 +1273,11 @@ function mapStateToProps(state: SharedAppState) {
|
||||
typeValueLookup: rulesEngineSelectors.getCharacterValueLookupByType(state),
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
|
||||
characterId: rulesEngineSelectors.getId(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
classes: rulesEngineSelectors.getClasses(state),
|
||||
containerLookup: rulesEngineSelectors.getContainerLookup(state),
|
||||
partyInventoryLookup: rulesEngineSelectors.getPartyInventoryLookup(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { ReactNode, PureComponent, PropsWithChildren, MouseEvent } from "react";
|
||||
|
||||
interface Props {
|
||||
header: React.ReactNode;
|
||||
extra?: React.ReactNode;
|
||||
interface Props extends PropsWithChildren {
|
||||
header: ReactNode;
|
||||
extra?: ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
export class InfusionChoicePaneStep extends React.PureComponent<Props> {
|
||||
handleClick = (evt: React.MouseEvent): void => {
|
||||
export class InfusionChoicePaneStep extends PureComponent<Props> {
|
||||
handleClick = (evt: MouseEvent): void => {
|
||||
const { onClick } = this.props;
|
||||
|
||||
if (onClick) {
|
||||
|
||||
-3
@@ -30,7 +30,6 @@ interface Props {
|
||||
infusionChoice: InfusionChoice;
|
||||
onItemSelected: (item: Item) => void;
|
||||
ruleData: RuleData;
|
||||
proficiencyBonus: number;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export class InfusionChoicePaneStore extends React.PureComponent<Props> {
|
||||
@@ -60,7 +59,6 @@ export class InfusionChoicePaneStore extends React.PureComponent<Props> {
|
||||
items,
|
||||
onItemSelected,
|
||||
ruleData,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
@@ -133,7 +131,6 @@ export class InfusionChoicePaneStore extends React.PureComponent<Props> {
|
||||
showActions={false}
|
||||
showImage={false}
|
||||
showAbilities={false}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import withAvailableItems from "./withAvailableItems";
|
||||
|
||||
export default withAvailableItems;
|
||||
export { withAvailableItems };
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import * as React from "react";
|
||||
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import DataLoadingStatusEnum from "../../../../constants/DataLoadingStatusEnum";
|
||||
import { AppLoggerUtils } from "../../../../utils";
|
||||
|
||||
function getDisplayName(WrappedComponent: React.ComponentType) {
|
||||
function getDisplayName(WrappedComponent: ComponentType) {
|
||||
return WrappedComponent.displayName || WrappedComponent.name || "Component";
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ interface State {
|
||||
availableItems: Array<Item>;
|
||||
}
|
||||
export function withAvailableItems<
|
||||
C extends React.ComponentType<React.ComponentProps<C>>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>
|
||||
C extends ComponentType<ComponentProps<C>>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
|
||||
>(WrappedComponent) {
|
||||
return class withAvailableItems extends React.PureComponent<
|
||||
return class withAvailableItems extends PureComponent<
|
||||
ResolvedProps & RequiredWithAvailableItemsProps,
|
||||
State
|
||||
> {
|
||||
@@ -115,7 +115,7 @@ export function withAvailableItems<
|
||||
itemLoadingStatus={loadingStatus}
|
||||
{...(this.props as JSX.LibraryManagedAttributes<
|
||||
C,
|
||||
React.ComponentProps<C>
|
||||
ComponentProps<C>
|
||||
>)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
import React, { useContext } from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
Action,
|
||||
ActionUtils,
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
ContainerLookup,
|
||||
DataOrigin,
|
||||
EntityValueLookup,
|
||||
HelperUtils,
|
||||
Infusion,
|
||||
InfusionChoiceLookup,
|
||||
InfusionUtils,
|
||||
InventoryManager,
|
||||
Item,
|
||||
ItemUtils,
|
||||
PartyInfo,
|
||||
RuleData,
|
||||
rulesEngineSelectors,
|
||||
serviceDataSelectors,
|
||||
SnippetData,
|
||||
Spell,
|
||||
WeaponSpellDamageGroup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { EditableName } from "~/components/EditableName";
|
||||
import { ItemName } from "~/components/ItemName";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Preview } from "~/subApps/sheet/components/Sidebar/components/Preview";
|
||||
import {
|
||||
getDataOriginComponentInfo,
|
||||
getSpellComponentInfo,
|
||||
} from "~/subApps/sheet/components/Sidebar/helpers/paneUtils";
|
||||
import {
|
||||
PaneComponentEnum,
|
||||
PaneIdentifiersItem,
|
||||
} from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import { PaneInitFailureContent } from "../../../../../../subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
|
||||
import ItemDetail from "../../../components/ItemDetail";
|
||||
import ItemListInformationCollapsible from "../../../components/ItemListInformationCollapsible";
|
||||
import { InventoryManagerContext } from "../../../managers/InventoryManagerContext";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../../utils";
|
||||
|
||||
interface Props {
|
||||
items: Array<Item>;
|
||||
weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
|
||||
ruleData: RuleData;
|
||||
snippetData: SnippetData;
|
||||
entityValueLookup: EntityValueLookup;
|
||||
infusionChoiceLookup: InfusionChoiceLookup;
|
||||
identifiers: PaneIdentifiersItem | null;
|
||||
isReadonly: boolean;
|
||||
proficiencyBonus: number;
|
||||
theme: CharacterTheme;
|
||||
containerLookup: ContainerLookup;
|
||||
inventoryManager: InventoryManager;
|
||||
partyInfo: PartyInfo;
|
||||
paneHistoryPush: PaneInfo["paneHistoryPush"];
|
||||
}
|
||||
interface State {
|
||||
item: Item | null;
|
||||
isCustomizeClosed: boolean;
|
||||
}
|
||||
class ItemPane extends React.PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = this.generateStateData(props, true);
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<Props>,
|
||||
prevState: Readonly<State>
|
||||
): void {
|
||||
const { items, identifiers } = this.props;
|
||||
|
||||
if (items !== prevProps.items || identifiers !== prevProps.identifiers) {
|
||||
this.setState(
|
||||
this.generateStateData(this.props, prevState.isCustomizeClosed)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
generateStateData = (props: Props, isCustomizeClosed: boolean): State => {
|
||||
const { items, identifiers } = props;
|
||||
|
||||
let foundItem: Item | null | undefined = null;
|
||||
if (identifiers !== null) {
|
||||
foundItem = items.find(
|
||||
(item) => identifiers.id === ItemUtils.getMappingId(item)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
item: foundItem ? foundItem : null,
|
||||
isCustomizeClosed,
|
||||
};
|
||||
};
|
||||
|
||||
handleCustomItemEdit = (adjustments: Record<string, any>): void => {
|
||||
const { item } = this.state;
|
||||
const { inventoryManager } = this.props;
|
||||
|
||||
if (item === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (adjustments.notes === "") {
|
||||
adjustments.notes = null;
|
||||
}
|
||||
|
||||
inventoryManager.handleCustomEdit({
|
||||
item,
|
||||
adjustments,
|
||||
});
|
||||
};
|
||||
|
||||
handleCustomDataUpdate = (
|
||||
adjustmentType: Constants.AdjustmentTypeEnum,
|
||||
value: any
|
||||
): void => {
|
||||
const { item } = this.state;
|
||||
const { inventoryManager } = this.props;
|
||||
|
||||
if (item) {
|
||||
inventoryManager.handleCustomizationSet({
|
||||
item,
|
||||
adjustmentType,
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleRemoveCustomizations = (): void => {
|
||||
const { item } = this.state;
|
||||
const { inventoryManager } = this.props;
|
||||
|
||||
if (item) {
|
||||
inventoryManager.handleCustomizationsRemove({ item });
|
||||
}
|
||||
};
|
||||
|
||||
handleDataOriginClick = (dataOrigin: DataOrigin) => {
|
||||
const { paneHistoryPush } = this.props;
|
||||
|
||||
let component = getDataOriginComponentInfo(dataOrigin);
|
||||
if (component.type !== PaneComponentEnum.ERROR_404) {
|
||||
paneHistoryPush(component.type, component.identifiers);
|
||||
}
|
||||
};
|
||||
|
||||
handleSpellClick = (spell: Spell): void => {
|
||||
const { paneHistoryPush } = this.props;
|
||||
|
||||
let component = getSpellComponentInfo(spell);
|
||||
if (component.type) {
|
||||
paneHistoryPush(component.type, component.identifiers);
|
||||
}
|
||||
};
|
||||
|
||||
handleInfusionClick = (infusion: Infusion): void => {
|
||||
const { paneHistoryPush } = this.props;
|
||||
|
||||
const choiceKey = InfusionUtils.getChoiceKey(infusion);
|
||||
if (choiceKey !== null) {
|
||||
paneHistoryPush(
|
||||
PaneComponentEnum.INFUSION_CHOICE,
|
||||
PaneIdentifierUtils.generateInfusionChoice(choiceKey)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleActionClick = (action: Action): void => {
|
||||
const { paneHistoryPush } = this.props;
|
||||
|
||||
const dataOrigin = ActionUtils.getDataOrigin(action);
|
||||
let component = getDataOriginComponentInfo(dataOrigin);
|
||||
if (component.type !== PaneComponentEnum.ERROR_404) {
|
||||
paneHistoryPush(component.type, component.identifiers);
|
||||
}
|
||||
};
|
||||
|
||||
handleOpenCustomize = () => {
|
||||
this.setState({ isCustomizeClosed: !this.state.isCustomizeClosed });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { item, isCustomizeClosed } = this.state;
|
||||
const {
|
||||
weaponSpellDamageGroups,
|
||||
ruleData,
|
||||
snippetData,
|
||||
entityValueLookup,
|
||||
infusionChoiceLookup,
|
||||
isReadonly,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
containerLookup,
|
||||
inventoryManager,
|
||||
partyInfo,
|
||||
} = this.props;
|
||||
|
||||
if (item === null) {
|
||||
return (
|
||||
<PaneInitFailureContent errorMessage="That item is no longer in your inventory! Please try again." />
|
||||
);
|
||||
}
|
||||
|
||||
const canCustomize = inventoryManager.canCustomizeItem(item) && !isReadonly;
|
||||
|
||||
return (
|
||||
<div className="ct-item-pane">
|
||||
<Header preview={<Preview imageUrl={ItemUtils.getAvatarUrl(item)} />}>
|
||||
<EditableName onClick={this.handleOpenCustomize}>
|
||||
<ItemName item={item} />
|
||||
</EditableName>
|
||||
</Header>
|
||||
<ItemListInformationCollapsible item={item} ruleData={ruleData} />
|
||||
<ItemDetail
|
||||
theme={theme}
|
||||
key={ItemUtils.getUniqueKey(item)}
|
||||
item={item}
|
||||
weaponSpellDamageGroups={weaponSpellDamageGroups}
|
||||
ruleData={ruleData}
|
||||
snippetData={snippetData}
|
||||
onCustomDataUpdate={this.handleCustomDataUpdate}
|
||||
onCustomizationsRemove={this.handleRemoveCustomizations}
|
||||
onDataOriginClick={this.handleDataOriginClick}
|
||||
onSpellClick={this.handleSpellClick}
|
||||
onInfusionClick={this.handleInfusionClick}
|
||||
onMasteryActionClick={this.handleActionClick}
|
||||
entityValueLookup={entityValueLookup}
|
||||
infusionChoiceLookup={infusionChoiceLookup}
|
||||
isReadonly={isReadonly}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
container={HelperUtils.lookupDataOrFallback(
|
||||
containerLookup,
|
||||
ItemUtils.getContainerDefinitionKey(item)
|
||||
)}
|
||||
showCustomize={canCustomize}
|
||||
onPostRemoveNavigation={PaneComponentEnum.EQUIPMENT_MANAGE}
|
||||
partyInfo={partyInfo}
|
||||
onCustomItemEdit={this.handleCustomItemEdit}
|
||||
isCustomizeClosed={isCustomizeClosed}
|
||||
onCustomizeClick={this.handleOpenCustomize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: SharedAppState) {
|
||||
return {
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
items: rulesEngineSelectors.getAllInventoryItems(state),
|
||||
weaponSpellDamageGroups:
|
||||
rulesEngineSelectors.getWeaponSpellDamageGroups(state),
|
||||
entityValueLookup:
|
||||
rulesEngineSelectors.getCharacterValueLookupByEntity(state),
|
||||
snippetData: rulesEngineSelectors.getSnippetData(state),
|
||||
infusionChoiceLookup: rulesEngineSelectors.getInfusionChoiceLookup(state),
|
||||
isReadonly: appEnvSelectors.getIsReadonly(state),
|
||||
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
containerLookup: rulesEngineSelectors.getContainerLookup(state),
|
||||
partyInfo: serviceDataSelectors.getPartyInfo(state),
|
||||
};
|
||||
}
|
||||
|
||||
const ItemPaneContainer = (props) => {
|
||||
const { inventoryManager } = useContext(InventoryManagerContext);
|
||||
const {
|
||||
pane: { paneHistoryPush },
|
||||
} = useSidebar();
|
||||
return (
|
||||
<ItemPane
|
||||
paneHistoryPush={paneHistoryPush}
|
||||
inventoryManager={inventoryManager}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(ItemPaneContainer);
|
||||
@@ -1,4 +0,0 @@
|
||||
import ItemPane from "./ItemPane";
|
||||
|
||||
export default ItemPane;
|
||||
export { ItemPane };
|
||||
@@ -1,243 +0,0 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
|
||||
import { Checkbox } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterUtils,
|
||||
ApiRequests,
|
||||
rulesEngineSelectors,
|
||||
characterActions,
|
||||
Condition,
|
||||
RuleData,
|
||||
ShortModelInfoContract,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { RestoreLifeManager } from "~/subApps/sheet/components/Sidebar/panes/HitPointsManagePane/RestoreLifeManager/RestoreLifeManager";
|
||||
|
||||
import { toastMessageActions } from "../../../actions/toastMessage";
|
||||
import { ThemeButton } from "../../../components/common/Button";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { AppLoggerUtils } from "../../../utils";
|
||||
|
||||
interface Props extends DispatchProp {
|
||||
activeConditions: Array<Condition>;
|
||||
ruleData: RuleData;
|
||||
isDead: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
interface State {
|
||||
resetMaxHpModifier: boolean;
|
||||
adjustConditionLevel: boolean;
|
||||
restMessage: string | null;
|
||||
}
|
||||
class LongRestPane extends React.PureComponent<Props, State> {
|
||||
loadMessageCanceler: null | Canceler = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
resetMaxHpModifier: true,
|
||||
adjustConditionLevel: false,
|
||||
restMessage: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadMessage();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.loadMessageCanceler !== null) {
|
||||
this.loadMessageCanceler();
|
||||
}
|
||||
}
|
||||
|
||||
loadMessage = (): void => {
|
||||
if (this.loadMessageCanceler !== null) {
|
||||
this.loadMessageCanceler();
|
||||
}
|
||||
|
||||
ApiRequests.getCharacterRestLong({
|
||||
cancelToken: new axios.CancelToken((c) => {
|
||||
this.loadMessageCanceler = c;
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
let message = ApiAdapterUtils.getResponseData(response);
|
||||
if (message !== null) {
|
||||
this.setState({
|
||||
restMessage: message,
|
||||
});
|
||||
}
|
||||
this.loadMessageCanceler = null;
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
};
|
||||
|
||||
reset = (): void => {
|
||||
this.setState({
|
||||
resetMaxHpModifier: true,
|
||||
});
|
||||
};
|
||||
|
||||
handleReset = (): void => {
|
||||
this.setState({
|
||||
resetMaxHpModifier: true,
|
||||
adjustConditionLevel: false,
|
||||
});
|
||||
};
|
||||
|
||||
handleSave = (): void => {
|
||||
const { resetMaxHpModifier, adjustConditionLevel } = this.state;
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(
|
||||
characterActions.longRest(resetMaxHpModifier, adjustConditionLevel)
|
||||
);
|
||||
dispatch(
|
||||
toastMessageActions.toastSuccess(
|
||||
"Long Rest Taken",
|
||||
"You have completed a long rest. Relevant abilities have been reset."
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
handleResetMaxHpChange = (enabled: boolean): void => {
|
||||
this.setState({
|
||||
resetMaxHpModifier: enabled,
|
||||
});
|
||||
};
|
||||
|
||||
handleAdjustExhaustionLevel = (enabled: boolean): void => {
|
||||
this.setState({
|
||||
adjustConditionLevel: enabled,
|
||||
});
|
||||
};
|
||||
|
||||
handleRestoreToLife = (restoreType: ShortModelInfoContract): void => {
|
||||
const { dispatch } = this.props;
|
||||
const restoreChoice = restoreType.name === "Full" ? "full" : "1";
|
||||
|
||||
dispatch(characterActions.restoreLife(restoreType.id));
|
||||
dispatch(
|
||||
toastMessageActions.toastSuccess(
|
||||
"Character Restored to Life",
|
||||
`You have been restored to life with ${restoreChoice} HP.`
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
renderRecover = (): React.ReactNode => {
|
||||
const { resetMaxHpModifier, adjustConditionLevel, restMessage } =
|
||||
this.state;
|
||||
|
||||
const { isDead, activeConditions } = this.props;
|
||||
|
||||
if (isDead) {
|
||||
return (
|
||||
<div className="ct-reset-pane__recover-sources">
|
||||
Your character is dead
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const exhaustionIsActive = activeConditions.find(
|
||||
(condition) => condition.level !== null
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="ct-reset-pane__recover-sources">
|
||||
{restMessage === null
|
||||
? "Asking the server what will be reset..."
|
||||
: restMessage}
|
||||
</div>
|
||||
<div className="ct-reset-pane__recover-max-hp">
|
||||
<Checkbox
|
||||
label="Reset Maximum HP changes during this rest"
|
||||
initiallyEnabled={resetMaxHpModifier}
|
||||
onChange={this.handleResetMaxHpChange}
|
||||
/>
|
||||
</div>
|
||||
{exhaustionIsActive && (
|
||||
<div className="ct-reset-pane__reset-exhaustion-level">
|
||||
<Checkbox
|
||||
label="Recover 1 Level of Exhaustion during this rest (requires food and drink)"
|
||||
initiallyEnabled={adjustConditionLevel}
|
||||
onChange={this.handleAdjustExhaustionLevel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderActions = (): React.ReactNode => {
|
||||
const { isDead, ruleData, theme } = this.props;
|
||||
|
||||
if (isDead) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<RestoreLifeManager onSave={this.handleRestoreToLife} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-reset-pane__actions">
|
||||
<div className="ct-reset-pane__action">
|
||||
<ThemeButton onClick={this.handleSave} enableConfirm={true}>
|
||||
Take Long Rest
|
||||
</ThemeButton>
|
||||
</div>
|
||||
<div className="ct-reset-pane__action">
|
||||
<ThemeButton onClick={this.handleReset} style="outline">
|
||||
Reset
|
||||
</ThemeButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-reset-pane">
|
||||
<Header>Long Rest</Header>
|
||||
|
||||
<div className="ct-reset-pane__intro">
|
||||
<p>
|
||||
A long rest is a period of extended downtime, at least 8 hours long,
|
||||
during which a character sleeps for at least 6 hours and performs no
|
||||
more than 2 hours of light activity, such as reading, talking,
|
||||
eating, or standing watch.
|
||||
</p>
|
||||
<p>
|
||||
If the rest is interrupted by a period of strenuous activity — at
|
||||
least 1 hour of walking, fighting, casting spells, or similar
|
||||
adventuring activity — the characters must begin the rest again to
|
||||
gain any benefit from it.
|
||||
</p>
|
||||
</div>
|
||||
<div className="ct-reset-pane__recover">
|
||||
<div className="ct-reset-pane__recover-heading">Recover</div>
|
||||
{this.renderRecover()}
|
||||
</div>
|
||||
{this.renderActions()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: SharedAppState) {
|
||||
return {
|
||||
activeConditions: rulesEngineSelectors.getActiveConditions(state),
|
||||
isDead: rulesEngineSelectors.isDead(state),
|
||||
ruleData: rulesEngineSelectors.getRuleData(state),
|
||||
theme: rulesEngineSelectors.getCharacterTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LongRestPane);
|
||||
@@ -1,4 +0,0 @@
|
||||
import LongRestPane from "./LongRestPane";
|
||||
|
||||
export default LongRestPane;
|
||||
export { LongRestPane };
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
characterActions,
|
||||
@@ -17,7 +18,9 @@ import { PaneInitFailureContent } from "../../../../../../subApps/sheet/componen
|
||||
import { ThemeButton } from "../../../components/common/Button";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
notes: CharacterNotes;
|
||||
placeholder: string;
|
||||
autoSaveDelay: number;
|
||||
@@ -192,7 +195,7 @@ class NoteManagePane extends React.PureComponent<Props, State> {
|
||||
if (hasBeenEdited) {
|
||||
if (isSaveDirty) {
|
||||
calloutNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-note-manage-pane__status ct-note-manage-pane__status--dirty">
|
||||
<div className="ct-note-manage-pane__status-text">
|
||||
Unsaved changes
|
||||
@@ -201,7 +204,7 @@ class NoteManagePane extends React.PureComponent<Props, State> {
|
||||
<ThemeButton size="small" onClick={this.handleSave}>
|
||||
Save
|
||||
</ThemeButton>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
calloutNode = (
|
||||
|
||||
+5
-5
@@ -1,5 +1,4 @@
|
||||
import React, { FC } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import {
|
||||
Constants,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
import { useSidebar } from "~/contexts/Sidebar/Sidebar";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { PaneInitFailureContent } from "~/subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
|
||||
import {
|
||||
@@ -40,7 +40,7 @@ export const PreferencesHitPointConfirmPane: FC<Props> = ({ identifiers }) => {
|
||||
switch (hitPointType) {
|
||||
case Constants.PreferenceHitPointTypeEnum.FIXED:
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>Fixed Hit Points</Header>
|
||||
<div className="change-preference-content">
|
||||
<p>
|
||||
@@ -49,12 +49,12 @@ export const PreferencesHitPointConfirmPane: FC<Props> = ({ identifiers }) => {
|
||||
</p>
|
||||
<p>Any rolled hit point totals will be lost.</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
|
||||
case Constants.PreferenceHitPointTypeEnum.MANUAL:
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>Manual Hit Points</Header>
|
||||
<div className="change-preference-content">
|
||||
<p>
|
||||
@@ -65,7 +65,7 @@ export const PreferencesHitPointConfirmPane: FC<Props> = ({ identifiers }) => {
|
||||
rolled values.
|
||||
</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
characterActions,
|
||||
@@ -19,7 +20,9 @@ import {
|
||||
import { SimpleClassSpellList } from "../../../components/SimpleClassSpellList";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
identifiers: PaneIdentifiersPreferenceOptionalClassFeaturesConfirm | null;
|
||||
classSpellListSpellsLookup: ClassSpellListSpellsLookup;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
@@ -81,7 +84,7 @@ class PreferencesOptionalClassFeaturesConfirmPane extends React.PureComponent<
|
||||
const { classSpellListSpellsLookup } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>
|
||||
{newIsEnabled ? "Enable" : "Disable"} Optional Class Features
|
||||
</Header>
|
||||
@@ -99,7 +102,7 @@ class PreferencesOptionalClassFeaturesConfirmPane extends React.PureComponent<
|
||||
classSpellListSpellsLookup={classSpellListSpellsLookup}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+6
-3
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
characterActions,
|
||||
@@ -18,7 +19,9 @@ import {
|
||||
import { SimpleClassSpellList } from "../../../components/SimpleClassSpellList";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
identifiers: PaneIdentifiersPreferenceOptionalOriginsConfirm | null;
|
||||
classSpellListSpellsLookup: ClassSpellListSpellsLookup;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
@@ -77,7 +80,7 @@ class PreferencesOptionalOriginsConfirmPane extends React.PureComponent<
|
||||
const { classSpellListSpellsLookup } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Header>
|
||||
{newIsEnabled ? "Enable" : "Disable"} Customized Origins
|
||||
</Header>
|
||||
@@ -95,7 +98,7 @@ class PreferencesOptionalOriginsConfirmPane extends React.PureComponent<
|
||||
classSpellListSpellsLookup={classSpellListSpellsLookup}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { PrivacyTypeRadio } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
AnySimpleDataType,
|
||||
characterActions,
|
||||
@@ -21,8 +22,9 @@ import {
|
||||
rulesEngineSelectors,
|
||||
RaceUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { Dice } from "@dndbeyond/dice";
|
||||
|
||||
import { CheckboxGroup, CheckboxInfo } from "~/components/CheckboxGroup";
|
||||
import { PrivacyTypeRadio } from "~/components/PrivacyTypeRadio";
|
||||
import { SourceCategoryDescription } from "~/constants";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
|
||||
@@ -31,17 +33,15 @@ import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
||||
|
||||
import config from "../../../../config";
|
||||
import { appEnvActions } from "../../../actions";
|
||||
import {
|
||||
CheckboxInfo,
|
||||
FormCheckBoxesField,
|
||||
} from "../../../components/common/FormCheckBoxesField";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../../utils";
|
||||
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
|
||||
import { ToggleInfo } from "./PreferencesPaneTogglesField";
|
||||
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> {
|
||||
activeSourceCategories: Array<number>;
|
||||
preferences: CharacterPreferences;
|
||||
ruleData: RuleData;
|
||||
@@ -52,10 +52,12 @@ interface Props extends DispatchProp {
|
||||
classSpellListSpellsLookup: ClassSpellListSpellsLookup;
|
||||
characterTheme: CharacterTheme;
|
||||
paneHistoryStart: PaneInfo["paneHistoryStart"];
|
||||
isVttView: boolean;
|
||||
}
|
||||
class PreferencesPane extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
diceEnabled: false,
|
||||
isVttView: false,
|
||||
};
|
||||
|
||||
handlePreferenceChange = (
|
||||
@@ -80,6 +82,7 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
handleSourceCategoryChange = (sourceId: number, isActive: boolean): void => {
|
||||
const { dispatch, activeSourceCategories } = this.props;
|
||||
let newSourceCats: Array<number> = [];
|
||||
|
||||
if (isActive) {
|
||||
newSourceCats = [...activeSourceCategories, sourceId];
|
||||
} else {
|
||||
@@ -208,7 +211,6 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
|
||||
try {
|
||||
localStorage.setItem("dice-enabled", newDiceEnabledSetting.toString());
|
||||
Dice.setEnabled(newDiceEnabledSetting);
|
||||
} catch (e) {}
|
||||
|
||||
dispatch(
|
||||
@@ -228,8 +230,13 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { preferences, activeSourceCategories, ruleData, diceEnabled } =
|
||||
this.props;
|
||||
const {
|
||||
preferences,
|
||||
activeSourceCategories,
|
||||
ruleData,
|
||||
diceEnabled,
|
||||
isVttView,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
useHomebrewContent,
|
||||
@@ -237,7 +244,6 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
hitPointType,
|
||||
progressionType,
|
||||
abilityScoreDisplayType,
|
||||
sharingType,
|
||||
privacyType,
|
||||
ignoreCoinWeight,
|
||||
enforceFeatRules,
|
||||
@@ -288,12 +294,6 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
},
|
||||
];
|
||||
|
||||
const sharingOptions: Array<HtmlSelectOption> = [
|
||||
{ label: "Full", value: Constants.PreferenceSharingTypeEnum.FULL },
|
||||
{ label: "Limited", value: Constants.PreferenceSharingTypeEnum.LIMITED },
|
||||
// {label: 'Stat Block', value: Constants.PreferenceSharingTypeEnum.STAT_BLOCK},
|
||||
];
|
||||
|
||||
let sourceToggles: Array<CheckboxInfo> = [];
|
||||
let partneredSourceToggles: Array<CheckboxInfo> = [];
|
||||
|
||||
@@ -302,10 +302,11 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggle: ToggleInfo = {
|
||||
const toggle: CheckboxInfo = {
|
||||
label: `${sourceCategory.name}`,
|
||||
initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
|
||||
onChange: this.handleSourceCategoryChange.bind(this, sourceCategory.id),
|
||||
onChange: (e) =>
|
||||
this.handleSourceCategoryChange(sourceCategory.id, e.target.checked),
|
||||
sortOrder: sourceCategory.sortOrder,
|
||||
};
|
||||
|
||||
@@ -319,96 +320,95 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
return (
|
||||
<div className="ct-preferences-pane">
|
||||
<Header>Preferences</Header>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Sources"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Sources"
|
||||
description={SourceCategoryDescription.official}
|
||||
name="source-categories"
|
||||
checkboxes={[
|
||||
...sourceToggles,
|
||||
{
|
||||
label: "Homebrew",
|
||||
initiallyEnabled: useHomebrewContent,
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"useHomebrewContent"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange(
|
||||
"useHomebrewContent",
|
||||
e.target.checked
|
||||
),
|
||||
sortOrder: 0,
|
||||
},
|
||||
...sourceToggles,
|
||||
]}
|
||||
themed={true}
|
||||
showAccordion={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
variant="collapse"
|
||||
accordionDefaultOpen={true}
|
||||
themed
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Partnered Content"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Partnered Content"
|
||||
description={SourceCategoryDescription.partnered}
|
||||
name="partnered-source-categories"
|
||||
checkboxes={partneredSourceToggles}
|
||||
checkUncheckAllEnabled={true}
|
||||
themed={true}
|
||||
accordionHeading="Categories"
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
showAccordion={true}
|
||||
variant="collapse"
|
||||
themed
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Underdark Mode"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Underdark Mode"
|
||||
description="Enables dark mode for this character"
|
||||
name="enable-dark-mode"
|
||||
variant="inline"
|
||||
data-testid="underdark-mode-toggle"
|
||||
checkboxes={[
|
||||
{
|
||||
initiallyEnabled: enableDarkMode,
|
||||
label: "Enable dark mode",
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"enableDarkMode"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange("enableDarkMode", e.target.checked),
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
themed
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Dice Rolling"
|
||||
description="Enables digital dice rolling for this character"
|
||||
checkboxes={[
|
||||
{
|
||||
initiallyEnabled: diceEnabled,
|
||||
label: "Enable Dice Rolling",
|
||||
onChange: this.handleDiceToggle,
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Optional Features"
|
||||
description="Allow or restrict optional features for this character."
|
||||
{!isVttView && (
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Dice Rolling"
|
||||
description="Enables digital dice rolling for all characters on this browser"
|
||||
name="enable-dice-rolling"
|
||||
variant="inline"
|
||||
checkboxes={[
|
||||
{
|
||||
initiallyEnabled: diceEnabled,
|
||||
label: "Enable Dice Rolling",
|
||||
onChange: this.handleDiceToggle,
|
||||
},
|
||||
]}
|
||||
themed
|
||||
/>
|
||||
)}
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Optional Features"
|
||||
description="Allow or restrict optional features for this character"
|
||||
name="optional-features"
|
||||
checkboxes={[
|
||||
{
|
||||
label: "Optional Class Features",
|
||||
initiallyEnabled: enableOptionalClassFeatures,
|
||||
onChangePromise:
|
||||
onChangeConfirm:
|
||||
this.handleOptionalClassFeaturesPreferenceChangePromise,
|
||||
},
|
||||
{
|
||||
label: "Customize Your Origin",
|
||||
initiallyEnabled: enableOptionalOrigins,
|
||||
onChangePromise:
|
||||
onChangeConfirm:
|
||||
this.handleOptionalOriginsPreferenceChangePromise,
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
themed
|
||||
/>
|
||||
|
||||
<PreferencesPaneSelectField
|
||||
className={styles.selectField}
|
||||
heading="Advancement Type"
|
||||
description="Story-based character progression / XP-based character progression"
|
||||
onChangePromise={this.handleProgressionPreferenceChangePromise}
|
||||
@@ -418,6 +418,7 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
block={true}
|
||||
/>
|
||||
<PreferencesPaneSelectField
|
||||
className={styles.selectField}
|
||||
heading="Hit Point Type"
|
||||
description="When leveling up, increase hit points by the fixed value for your chosen class or manually enter a rolled value"
|
||||
onChangePromise={this.handleHitPointPreferenceChangePromise}
|
||||
@@ -427,88 +428,93 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
block={true}
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Use Prerequisites"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Use Prerequisites"
|
||||
description="Allow or restrict choices based on rule prerequisites for the following for this character"
|
||||
name="use-prerequisites"
|
||||
checkboxes={[
|
||||
{
|
||||
label: "Feats",
|
||||
initiallyEnabled: enforceFeatRules,
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"enforceFeatRules"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange(
|
||||
"enforceFeatRules",
|
||||
e.target.checked
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Multiclass Requirements",
|
||||
initiallyEnabled: enforceMulticlassRules,
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"enforceMulticlassRules"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange(
|
||||
"enforceMulticlassRules",
|
||||
e.target.checked
|
||||
),
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Show level-scaled spells"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Show level-scaled spells"
|
||||
description="Display and highlight available spells to cast with higher level spell slots"
|
||||
name="show-scaled-spells"
|
||||
variant="inline"
|
||||
checkboxes={[
|
||||
{
|
||||
initiallyEnabled: showScaledSpells,
|
||||
label: "Show Level-Scaled Spells",
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"showScaledSpells"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange(
|
||||
"showScaledSpells",
|
||||
e.target.checked
|
||||
),
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
themed
|
||||
/>
|
||||
|
||||
<PreferencesPaneSelectField
|
||||
className={styles.selectField}
|
||||
heading="Encumbrance Type"
|
||||
description="Use the standard encumbrance rules / Disable the encumbrance display / Use the more detailed rules for encumbrance"
|
||||
onChange={this.handleIntPreferenceChange.bind(
|
||||
this,
|
||||
"encumbranceType"
|
||||
)}
|
||||
onChange={(value) =>
|
||||
this.handleIntPreferenceChange("encumbranceType", value)
|
||||
}
|
||||
initialOptionRemoved={true}
|
||||
options={encumbranceOptions}
|
||||
initialValue={encumbranceType}
|
||||
block={true}
|
||||
/>
|
||||
|
||||
<FormCheckBoxesField
|
||||
heading="Ignore Coin Weight"
|
||||
<CheckboxGroup
|
||||
className={styles.checkboxGroup}
|
||||
title="Ignore Coin Weight"
|
||||
description="Coins do not count against your total weight carried (50 coins weigh 1 lb.)"
|
||||
name="ignore-coin-weight"
|
||||
variant="inline"
|
||||
checkboxes={[
|
||||
{
|
||||
initiallyEnabled: ignoreCoinWeight,
|
||||
label: "Ignore Coin Weight",
|
||||
onChange: this.handlePreferenceChange.bind(
|
||||
this,
|
||||
"ignoreCoinWeight"
|
||||
),
|
||||
onChange: (e) =>
|
||||
this.handlePreferenceChange(
|
||||
"ignoreCoinWeight",
|
||||
e.target.checked
|
||||
),
|
||||
},
|
||||
]}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
darkMode={enableDarkMode}
|
||||
themed
|
||||
/>
|
||||
|
||||
<PreferencesPaneSelectField
|
||||
className={styles.selectField}
|
||||
heading="Ability Score/Modifier Display"
|
||||
description="Reverse the arrangement of ability modifiers and scores"
|
||||
onChange={this.handleIntPreferenceChange.bind(
|
||||
this,
|
||||
"abilityScoreDisplayType"
|
||||
)}
|
||||
onChange={(value) =>
|
||||
this.handleIntPreferenceChange("abilityScoreDisplayType", value)
|
||||
}
|
||||
initialOptionRemoved={true}
|
||||
options={abilityDisplayOptions}
|
||||
initialValue={abilityScoreDisplayType}
|
||||
@@ -516,21 +522,20 @@ class PreferencesPane extends React.PureComponent<Props> {
|
||||
/>
|
||||
|
||||
<PrivacyTypeRadio
|
||||
handleChange={(e) =>
|
||||
this.handleChangePrivacy(parseInt(e?.target?.value))
|
||||
}
|
||||
darkMode={enableDarkMode}
|
||||
className={styles.privacyTypeRadio}
|
||||
initialValue={privacyType}
|
||||
onChange={(e) =>
|
||||
this.handleChangePrivacy(
|
||||
HelperUtils.parseInputInt(e?.target?.value)
|
||||
)
|
||||
}
|
||||
themed={true}
|
||||
variant="sidebar"
|
||||
/>
|
||||
|
||||
<div className="ct-preferences-pane__version">
|
||||
<div className="ct-preferences-pane__version-label">Version:</div>
|
||||
<div className="ct-preferences-pane__version-value">
|
||||
{config.version}
|
||||
</div>
|
||||
</div>
|
||||
<footer className={styles.footer}>
|
||||
<strong>Version:</strong>
|
||||
{config.version || "@dndbeyond/character-app@dev"}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -556,7 +561,15 @@ const PreferencesPaneContainer = (props) => {
|
||||
const {
|
||||
pane: { paneHistoryStart },
|
||||
} = useSidebar();
|
||||
return <PreferencesPane paneHistoryStart={paneHistoryStart} {...props} />;
|
||||
const [searchParams] = useSearchParams();
|
||||
const isVttView = searchParams.get("view") === "vtt";
|
||||
return (
|
||||
<PreferencesPane
|
||||
paneHistoryStart={paneHistoryStart}
|
||||
isVttView={isVttView}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(PreferencesPaneContainer);
|
||||
|
||||
+4
-3
@@ -1,12 +1,12 @@
|
||||
import { uniqueId } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
HelperUtils,
|
||||
HtmlSelectOption,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
interface Props {
|
||||
@@ -141,12 +141,13 @@ export default class PreferencesPaneSelectField extends React.PureComponent<
|
||||
<Select
|
||||
options={options}
|
||||
onChange={onChange ? this.handleChange : undefined}
|
||||
onChangePromise={
|
||||
onChangeConfirm={
|
||||
onChangePromise ? this.handleChangePromise : undefined
|
||||
}
|
||||
value={value}
|
||||
initialOptionRemoved={initialOptionRemoved}
|
||||
hidePlaceholderOption={initialOptionRemoved}
|
||||
placeholder={placeholder}
|
||||
name={uId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { Toggle } from "~/components/Toggle";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
import { ToggleInfo } from "./typings";
|
||||
|
||||
interface Props {
|
||||
toggles: Array<ToggleInfo>;
|
||||
heading: React.ReactNode;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class PreferencesPaneTogglesField extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
initiallyEnabled: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { heading, description, toggles } = this.props;
|
||||
|
||||
if (!toggles.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedToggles = orderBy(toggles, "sortOrder");
|
||||
|
||||
return (
|
||||
<div className="ct-preferences-pane__field ct-preferences-pane__field--toggle">
|
||||
<div className="ct-preferences-pane__field-summary">
|
||||
<Heading className="ct-preferences-pane__field-heading">
|
||||
{heading}
|
||||
</Heading>
|
||||
{description && (
|
||||
<div className="ct-preferences-pane__field-description">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ct-preferences-pane__field-toggles">
|
||||
{sortedToggles.map((toggle, idx) => (
|
||||
<div className="ct-preferences-pane__field-toggle" key={idx}>
|
||||
<div className="ct-preferences-pane__field-input">
|
||||
<Toggle
|
||||
checked={toggle.initiallyEnabled}
|
||||
onClick={toggle.onChange}
|
||||
onChangePromise={toggle.onChangePromise}
|
||||
color="themed"
|
||||
aria-label={toggle.label}
|
||||
/>
|
||||
</div>
|
||||
<div className="ct-preferences-pane__field-label">
|
||||
{toggle.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
import PreferencesPaneTogglesField from "./PreferencesPaneTogglesField";
|
||||
|
||||
export * from "./typings";
|
||||
export default PreferencesPaneTogglesField;
|
||||
export { PreferencesPaneTogglesField };
|
||||
@@ -1,12 +1,10 @@
|
||||
import PreferencesPane from "./PreferencesPane";
|
||||
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
|
||||
import PreferencesPaneToggleField from "./PreferencesPaneToggleField";
|
||||
import PreferencesPaneTogglesField from "./PreferencesPaneTogglesField";
|
||||
|
||||
export default PreferencesPane;
|
||||
export {
|
||||
PreferencesPane,
|
||||
PreferencesPaneSelectField,
|
||||
PreferencesPaneToggleField,
|
||||
PreferencesPaneTogglesField,
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,5 +1,4 @@
|
||||
import { FC } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import {
|
||||
Constants,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { PaneInitFailureContent } from "~/subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Select } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
characterActions,
|
||||
CharacterValuesContract,
|
||||
@@ -19,6 +20,8 @@ import {
|
||||
ValueUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Select } from "~/components/Select";
|
||||
import formStyles from "~/subApps/builder/styles/form.module.css";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
@@ -26,6 +29,7 @@ import ProficiencyGroups from "../../../../CharacterSheet/components/Proficiency
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import ProficienciesPaneCustomProficiency from "./ProficienciesPaneCustomProficiency";
|
||||
import ProficienciesPaneExistingProficiency from "./ProficienciesPaneExistingProficiency";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export const PROFICIENCY_TYPE = {
|
||||
ARMOR: 1,
|
||||
@@ -36,7 +40,9 @@ export const PROFICIENCY_TYPE = {
|
||||
LANGUAGE_OTHER: 6,
|
||||
};
|
||||
|
||||
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> {
|
||||
customProficiencies: Array<CustomProficiencyContract>;
|
||||
proficiencyGroups: Array<ProficiencyGroup>;
|
||||
typeValueLookup: TypeValueLookup;
|
||||
@@ -372,29 +378,31 @@ class ProficienciesPane extends React.PureComponent<Props, State> {
|
||||
let subtypeOptions = this.getSubtypeOptions();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Heading>Add New Proficiencies</Heading>
|
||||
<div className="ct-proficiencies-pane__add">
|
||||
<div className="ct-proficiencies-pane__add-field ct-proficiencies-pane__add-field--type">
|
||||
<label className={formStyles.label}>Proficiency Type</label>
|
||||
<Select
|
||||
className={styles.select}
|
||||
options={typeOptions}
|
||||
onChange={this.handleTypeChange}
|
||||
value={type}
|
||||
name="proficiency-type-select"
|
||||
/>
|
||||
{subtypeOptions.length > 0 && (
|
||||
<>
|
||||
<label className={clsx([formStyles.label, styles.subtype])}>
|
||||
Available Proficiencies
|
||||
</label>
|
||||
<Select
|
||||
options={typeOptions}
|
||||
onChange={this.handleTypeChange}
|
||||
value={type}
|
||||
className={styles.subtype}
|
||||
options={subtypeOptions}
|
||||
onChange={this.handleSubtypeChange}
|
||||
value={subtype}
|
||||
name="proficiency-subtype-select"
|
||||
/>
|
||||
</div>
|
||||
{subtypeOptions.length > 0 && (
|
||||
<div className="ct-proficiencies-pane__add-field ct-proficiencies-pane__add-field--subtype">
|
||||
<Select
|
||||
options={subtypeOptions}
|
||||
onChange={this.handleSubtypeChange}
|
||||
value={subtype}
|
||||
initialOptionRemoved={true}
|
||||
resetAfterChoice={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Collapsible } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
@@ -28,7 +29,9 @@ import * as appEnvSelectors from "../../../selectors/appEnv";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { SenseManagePaneCustomizeItem } from "./SenseManagePaneCustomizeItem";
|
||||
|
||||
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> {
|
||||
passivePerception: number | null;
|
||||
passiveInvestigation: number | null;
|
||||
passiveInsight: number | null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
characterActions,
|
||||
@@ -99,7 +100,9 @@ function generateSettingsContexts(
|
||||
return SETTINGS_CONTEXTS;
|
||||
}
|
||||
|
||||
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> {
|
||||
// Figure out if this is right
|
||||
preferences: CharacterPreferences;
|
||||
identifiers: PaneIdentifiersSettingsContext;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { FC, HTMLAttributes, useEffect, useRef, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import {
|
||||
LoadingPlaceholder,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { useDispatch } from "~/hooks/useDispatch";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
|
||||
import { ClipboardUtils } from "../../../utils";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
@@ -22,9 +23,12 @@ import {
|
||||
ShortModelInfoContract,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import { RollRequest } from "@dndbeyond/dice";
|
||||
import { IRollContext, RollType } from "@dndbeyond/dice";
|
||||
import { GameLogContext } from "@dndbeyond/game-log-components";
|
||||
import { RollTypes } from "@dndbeyond/pocket-dimension-dice/constants";
|
||||
import {
|
||||
RollContext,
|
||||
RollDicePayload,
|
||||
} from "@dndbeyond/pocket-dimension-dice/types";
|
||||
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { RestoreLifeManager } from "~/subApps/sheet/components/Sidebar/panes/HitPointsManagePane/RestoreLifeManager/RestoreLifeManager";
|
||||
@@ -39,14 +43,16 @@ import {
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { AppLoggerUtils } from "../../../utils";
|
||||
|
||||
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> {
|
||||
classes: Array<CharClass>;
|
||||
conModifier: number | null;
|
||||
ruleData: RuleData;
|
||||
isDead: boolean;
|
||||
theme: CharacterTheme;
|
||||
diceEnabled: boolean;
|
||||
characterRollContext: IRollContext;
|
||||
characterRollContext: RollContext;
|
||||
hitPointInfo: HitPointInfo;
|
||||
}
|
||||
interface State {
|
||||
@@ -224,7 +230,7 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-reset-pane__recover-sources">
|
||||
{restMessage === null
|
||||
? "Asking the server what will be reset..."
|
||||
@@ -244,7 +250,7 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -270,9 +276,6 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
hitPointInfo,
|
||||
} = this.props;
|
||||
|
||||
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
|
||||
this.context;
|
||||
|
||||
if (isDead) {
|
||||
return null;
|
||||
}
|
||||
@@ -327,13 +330,11 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
ClassUtils.getMappingId(charClass)
|
||||
] = ref;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}}
|
||||
className="ct-reset-pane__hitdie-manager-dice"
|
||||
>
|
||||
<DigitalDiceWrapper
|
||||
useAdvancedMenu={false}
|
||||
isContextMenuEnabled={false}
|
||||
diceNotation={DiceUtils.renderDice({
|
||||
...hitDice,
|
||||
diceCount:
|
||||
@@ -345,7 +346,9 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
ClassUtils.getMappingId(charClass)
|
||||
] * conModifier,
|
||||
})}
|
||||
onRollResults={(rollRequest: RollRequest) => {
|
||||
rollCallback={(
|
||||
rollRequest: RollDicePayload["data"]
|
||||
) => {
|
||||
if (
|
||||
healCharacterOnHitDieRoll &&
|
||||
rollRequest.rolls[0]?.result?.total
|
||||
@@ -375,17 +378,12 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
};
|
||||
});
|
||||
}}
|
||||
rollType={RollType.Heal}
|
||||
rollAction={"Hit Dice"}
|
||||
diceEnabled={diceEnabled}
|
||||
rollContext={characterRollContext}
|
||||
rollTargetOptions={
|
||||
messageTargetOptions
|
||||
? Object.values(messageTargetOptions?.entities)
|
||||
: undefined
|
||||
}
|
||||
rollTargetDefault={defaultMessageTargetOption}
|
||||
userId={userId}
|
||||
rollType={RollTypes.Heal}
|
||||
action="Hit Dice"
|
||||
isDiceEnabled={diceEnabled}
|
||||
entityId={String(characterRollContext.entityId)}
|
||||
entityType={String(characterRollContext.entityType)}
|
||||
name={String(characterRollContext.name)}
|
||||
>
|
||||
{DiceUtils.renderDice({
|
||||
...hitDice,
|
||||
@@ -415,9 +413,9 @@ class ShortRestPane extends React.PureComponent<Props, State> {
|
||||
|
||||
if (isDead) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<RestoreLifeManager onSave={this.handleRestoreToLife} />
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -38,7 +39,9 @@ import ValueEditor from "../../../components/ValueEditor";
|
||||
import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
skills: Array<Skill>;
|
||||
ruleData: RuleData;
|
||||
entityValueLookup: EntityValueLookup;
|
||||
@@ -168,7 +171,7 @@ class SkillPane extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{diceAdjustments.map((diceAdjustment, idx) => {
|
||||
return (
|
||||
<DiceAdjustmentSummary
|
||||
@@ -180,7 +183,7 @@ class SkillPane extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -30,7 +31,9 @@ import { appEnvSelectors } from "../../../selectors";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import { PaneIdentifierUtils } from "../../../utils";
|
||||
|
||||
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> {
|
||||
skills: Array<Skill>;
|
||||
customSkills: Array<Skill>;
|
||||
ruleData: RuleData;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import { Collapsible, Select } from "@dndbeyond/character-components/es";
|
||||
import { Collapsible } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
characterActions,
|
||||
CharacterPreferences,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { NumberDisplay } from "~/components/NumberDisplay";
|
||||
import { Select } from "~/components/Select";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
|
||||
|
||||
@@ -27,7 +29,9 @@ import * as appEnvSelectors from "../../../selectors/appEnv";
|
||||
import { SharedAppState } from "../../../stores/typings";
|
||||
import SpeedManagePaneCustomizeItem from "./SpeedManagePaneCustomizeItem";
|
||||
|
||||
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> {
|
||||
movements: SpeedInfo;
|
||||
customSpeedLookup: CustomSpeedLookup;
|
||||
customSpeeds: Array<CustomSpeedContract>;
|
||||
@@ -129,8 +133,9 @@ class SpeedManagePane extends React.PureComponent<Props> {
|
||||
<Select
|
||||
options={movementOptions}
|
||||
onChange={this.handleMovementDisplayChange}
|
||||
initialOptionRemoved={true}
|
||||
value={preferences.primaryMovement}
|
||||
name="movement-display"
|
||||
hidePlaceholderOption
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
@@ -154,7 +159,7 @@ class SpeedManagePane extends React.PureComponent<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { movements, customSpeedLookup, ruleData, theme } = this.props;
|
||||
const { movements, customSpeedLookup, ruleData } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-speed-manage-pane">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect, DispatchProp } from "react-redux";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import {
|
||||
Background,
|
||||
@@ -21,7 +22,9 @@ import SuggestionTable from "../../../components/SuggestionTable";
|
||||
import { ThemeButton } from "../../../components/common/Button";
|
||||
import { SharedAppState } from "../../../stores/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> {
|
||||
identifiers: PaneIdentifiersTrait;
|
||||
background: Background | null;
|
||||
traits: CharacterTraits;
|
||||
@@ -255,14 +258,14 @@ class TraitPane extends React.PureComponent<Props, State> {
|
||||
if (hasBeenEdited) {
|
||||
if (isSaveDirty) {
|
||||
calloutNode = (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className="ct-trait-pane__status ct-trait-pane__status--dirty">
|
||||
<div className="ct-trait-pane__status-text">Unsaved changes</div>
|
||||
</div>
|
||||
<ThemeButton size="small" onClick={this.handleSave}>
|
||||
Save
|
||||
</ThemeButton>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
calloutNode = (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ArrowRightAltIcon from "@mui/icons-material/ArrowRightAlt";
|
||||
import { has } from "lodash";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
@@ -6,7 +5,6 @@ import { connect } from "react-redux";
|
||||
import {
|
||||
Collapsible,
|
||||
DisabledLockSvg,
|
||||
MarketplaceCta,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
@@ -19,10 +17,12 @@ import {
|
||||
VehicleComponentManager,
|
||||
VehicleManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
import LinkIcon from "@dndbeyond/fontawesome-cache/svgs/solid/arrow-up-right-from-square.svg";
|
||||
|
||||
import { EditableName } from "~/components/EditableName";
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { Link } from "~/components/Link";
|
||||
import { MarketplaceCta } from "~/components/MarketplaceCta";
|
||||
import { Reference } from "~/components/Reference";
|
||||
import { TagGroup } from "~/components/TagGroup";
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
PaneIdentifierUtils,
|
||||
TypeScriptUtils,
|
||||
} from "../../../utils";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Hack_DataForEditor {
|
||||
name: string | null;
|
||||
@@ -512,7 +513,7 @@ class VehiclePane extends React.PureComponent<Props, State> {
|
||||
vehicle.isSpelljammer() || vehicle.isElementalAirship();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
{this.renderCustomize()}
|
||||
{primaryComponentManageType ===
|
||||
Constants.VehicleConfigurationPrimaryComponentManageTypeEnum
|
||||
@@ -533,20 +534,16 @@ class VehiclePane extends React.PureComponent<Props, State> {
|
||||
{this.renderTags()}
|
||||
{this.renderDescription()}
|
||||
<Link
|
||||
className={styles.link}
|
||||
href={vehicle.getUrl() ?? ""}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginTop: "8px",
|
||||
color: "primary.main",
|
||||
}}
|
||||
>
|
||||
{vehicle.getDefinitionName()} Details Page <ArrowRightAltIcon />
|
||||
{vehicle.getDefinitionName()} Details Page{" "}
|
||||
<LinkIcon className={styles.linkIcon} />
|
||||
</Link>
|
||||
{this.renderActions()}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -566,10 +563,10 @@ class VehiclePane extends React.PureComponent<Props, State> {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<DisabledLockSvg className="ct-vehicle-pane__header-lock" />
|
||||
{name}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user