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