Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,41 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect, DispatchProp } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import Actions from "../../Actions";
interface Props extends DispatchProp {
theme: CharacterTheme;
}
class ActionsMobile extends React.PureComponent<Props> {
render() {
const { theme } = this.props;
return (
<SubsectionMobile>
<section className="ct-actions-mobile">
<h2 style={visuallyHidden}>Actions</h2>
<MobileDivider label="Actions" theme={theme} />
<Actions showNotes={false} />
<MobileDivider isEnd={true} theme={theme} />
</section>
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
export default connect(mapStateToProps)(ActionsMobile);
@@ -0,0 +1,4 @@
import ActionsMobile from "./ActionsMobile";
export default ActionsMobile;
export { ActionsMobile };
@@ -0,0 +1,244 @@
import clsx from "clsx";
import React from "react";
import { connect, DispatchProp } from "react-redux";
import {
CharacterPreferences,
SpeedInfo,
RuleData,
RuleDataUtils,
rulesEngineSelectors,
CharacterTheme,
characterSelectors,
CharacterStatusSlug,
} from "@dndbeyond/character-rules-engine/es";
import { GameLogContext } from "@dndbeyond/game-log-components";
import { NumberDisplay } from "~/components/NumberDisplay";
import { PremadeCharacterEditStatus } from "~/components/PremadeCharacterEditStatus";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { InitiativeBox } from "~/subApps/sheet/components/InitiativeBox";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { appEnvSelectors } from "../../../../Shared/selectors";
import { SheetAppState } from "../../../typings";
import styles from "./styles.module.css";
interface Props extends DispatchProp {
armorClass: number;
speeds: SpeedInfo;
proficiencyBonus: number;
preferences: CharacterPreferences;
ruleData: RuleData;
diceEnabled: boolean;
theme: CharacterTheme;
paneHistoryStart: PaneInfo["paneHistoryStart"];
isReadonly: boolean;
characterStatus: CharacterStatusSlug | null;
}
class CombatMobile extends React.PureComponent<Props> {
static defaultProps = {
diceEnabled: false,
};
handleProficiencyBonusClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.PROFICIENCY_BONUS);
};
handleSpeedsClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.SPEED_MANAGE);
};
handleDefensesSummaryClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.DEFENSE_MANAGE);
};
handleConditionSummaryClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.CONDITION_MANAGE);
};
handleArmorClassClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.ARMOR_MANAGE);
};
handleInitiativeClick = (evt: React.MouseEvent): void => {
const { paneHistoryStart } = this.props;
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
paneHistoryStart(PaneComponentEnum.INITIATIVE);
};
renderProficiency = (): React.ReactNode => {
const { proficiencyBonus } = this.props;
return (
<div
className="ct-combat-mobile__extra ct-combat-mobile__extra--proficiency"
onClick={this.handleProficiencyBonusClick}
>
<div className="ct-combat-mobile__extra-heading">
<div className="ct-combat-mobile__extra-label">Proficiency</div>
</div>
<div className="ct-combat-mobile__extra-value">
<NumberDisplay
type="signed"
number={proficiencyBonus}
size={"large"}
className={clsx([
!this.props.theme.isDarkMode && styles.numberColorOverride,
])}
/>
</div>
<div className="ct-combat-mobile__extra-footer">
<div className="ct-combat-mobile__extra-label">Bonus</div>
</div>
</div>
);
};
renderSpeed = (): React.ReactNode => {
const { preferences, speeds, ruleData, theme } = this.props;
const displaySpeedValue: number =
speeds[
RuleDataUtils.getSpeedMovementKeyById(preferences.primaryMovement)
];
const displaySpeedLabel = RuleDataUtils.getMovementDescription(
preferences.primaryMovement,
ruleData
);
return (
<div
className="ct-combat-mobile__extra ct-combat-mobile__extra--speed"
onClick={this.handleSpeedsClick}
>
<div className="ct-combat-mobile__extra-heading">
<div className="ct-combat-mobile__extra-subheading">
{displaySpeedLabel}
</div>
</div>
<div className="ct-combat-mobile__extra-value">
<NumberDisplay
type="distanceInFt"
number={displaySpeedValue}
size={"large"}
className={clsx([
!this.props.theme.isDarkMode && styles.numberColorOverride,
])}
/>
</div>
<div className="ct-combat-mobile__extra-footer">
<div className="ct-combat-mobile__extra-label">Speed</div>
</div>
</div>
);
};
renderArmorClass = (): React.ReactNode => {
const { armorClass } = this.props;
return (
<div
className="ct-combat-mobile__extra ct-combat-mobile__extra--ac"
onClick={this.handleArmorClassClick}
>
<div className="ct-combat-mobile__extra-heading">
<div className="ct-combat-mobile__extra-label">Armor</div>
</div>
<div
className="ct-combat-mobile__extra-value"
data-testid="armor-class-value"
>
{armorClass}
</div>
<div className="ct-combat-mobile__extra-footer">
<div className="ct-combat-mobile__extra-label">Class</div>
</div>
</div>
);
};
render() {
const { theme, isReadonly, characterStatus } = this.props;
return (
<div
className={`ct-combat-mobile ${
theme.isDarkMode ? "ct-combat-mobile--dark-mode" : ""
}`}
>
<div className="ct-combat-mobile__extras">
{this.renderProficiency()}
{this.renderSpeed()}
<InitiativeBox isMobile={true} />
{this.renderArmorClass()}
<div className="ct-combat-mobile__extra ct-combat-mobile__extra--statuses">
<div className="ct-combat-mobile__ctas">
<div className="ct-combat-mobile__cta">
<div
className="ct-combat-mobile__cta-button"
onClick={this.handleDefensesSummaryClick}
>
Defenses
</div>
</div>
<div className="ct-combat-mobile__cta">
<div
className="ct-combat-mobile__cta-button"
onClick={this.handleConditionSummaryClick}
>
Conditions
</div>
</div>
</div>
</div>
</div>
<PremadeCharacterEditStatus
characterStatus={characterStatus}
isReadonly={isReadonly}
/>
</div>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
armorClass: rulesEngineSelectors.getAcTotal(state),
proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
speeds: rulesEngineSelectors.getCurrentCarriedWeightSpeed(state),
preferences: rulesEngineSelectors.getCharacterPreferences(state),
ruleData: rulesEngineSelectors.getRuleData(state),
diceEnabled: appEnvSelectors.getDiceEnabled(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
characterStatus: characterSelectors.getStatusSlug(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
};
}
const CombatMobileContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <CombatMobile {...props} paneHistoryStart={paneHistoryStart} />;
};
CombatMobileContainer.contextType = GameLogContext;
export default connect(mapStateToProps)(CombatMobileContainer);
@@ -0,0 +1,4 @@
import CombatMobile from "./CombatMobile";
export default CombatMobile;
export { CombatMobile };
@@ -0,0 +1,22 @@
import { visuallyHidden } from "@mui/utils";
import { FC } from "react";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import Description from "../../Description";
export const DescriptionMobile: FC = () => {
const { characterTheme } = useCharacterEngine();
return (
<SubsectionMobile className="ct-description-mobile">
<MobileDivider label={"Background"} theme={characterTheme} />
<section>
<h2 style={visuallyHidden}>Background</h2>
<Description theme={characterTheme} isVertical={true} />
</section>
<MobileDivider isEnd={true} theme={characterTheme} />
</SubsectionMobile>
);
};
@@ -0,0 +1,68 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { appEnvSelectors } from "../../../../Shared/selectors";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import { Equipment } from "../../Equipment";
interface Props {
isReadonly: boolean;
theme: CharacterTheme;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class EquipmentMobile extends React.PureComponent<Props> {
handleManageClick = (): void => {
const { paneHistoryStart, isReadonly } = this.props;
if (!isReadonly) {
paneHistoryStart(PaneComponentEnum.EQUIPMENT_MANAGE);
}
};
render() {
const { isReadonly, theme } = this.props;
return (
<SubsectionMobile>
<MobileDivider
label="Inventory"
onClick={this.handleManageClick}
isReadonly={isReadonly}
theme={theme}
/>
<section className="ct-equipment-mobile">
<h2 style={visuallyHidden}>Inventory</h2>
<Equipment showNotes={false} />
</section>
<MobileDivider isEnd={true} theme={theme} />
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
isReadonly: appEnvSelectors.getIsReadonly(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
const EquipmentMobileContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <EquipmentMobile paneHistoryStart={paneHistoryStart} {...props} />;
};
export default connect(mapStateToProps)(EquipmentMobileContainer);
@@ -0,0 +1,4 @@
import EquipmentMobile from "./EquipmentMobile";
export default EquipmentMobile;
export { EquipmentMobile };
@@ -0,0 +1,68 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { appEnvSelectors } from "../../../../Shared/selectors";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import Extras from "../../Extras";
interface Props {
isReadonly: boolean;
theme: CharacterTheme;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class ExtrasMobile extends React.PureComponent<Props> {
handleManageOpen = (): void => {
const { paneHistoryStart, isReadonly } = this.props;
if (!isReadonly) {
paneHistoryStart(PaneComponentEnum.EXTRA_MANAGE);
}
};
render() {
const { isReadonly, theme } = this.props;
return (
<SubsectionMobile>
<MobileDivider
label="Extras"
onClick={this.handleManageOpen}
isReadonly={isReadonly}
theme={theme}
/>
<section className="ct-extras-mobile">
<h2 style={visuallyHidden}>Extras</h2>
<Extras showNotes={false} />
</section>
<MobileDivider isEnd={true} theme={theme} />
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
isReadonly: appEnvSelectors.getIsReadonly(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
const ExtrasMobileContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <ExtrasMobile paneHistoryStart={paneHistoryStart} {...props} />;
};
export default connect(mapStateToProps)(ExtrasMobileContainer);
@@ -0,0 +1,4 @@
import ExtrasMobile from "./ExtrasMobile";
export default ExtrasMobile;
export { ExtrasMobile };
@@ -0,0 +1,41 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect, DispatchProp } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import Features from "../../Features";
interface Props extends DispatchProp {
theme: CharacterTheme;
}
class FeaturesMobile extends React.PureComponent<Props> {
render() {
const { theme } = this.props;
return (
<SubsectionMobile>
<MobileDivider label="Features & Traits" theme={theme} />
<section className="ct-features-mobile">
<h2 style={visuallyHidden}>Features and Traits</h2>
<Features />
</section>
<MobileDivider isEnd={true} theme={theme} />
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
export default connect(mapStateToProps)(FeaturesMobile);
@@ -0,0 +1,4 @@
import FeaturesMobile from "./FeaturesMobile";
export default FeaturesMobile;
export { FeaturesMobile };
@@ -0,0 +1,176 @@
import { visuallyHidden } from "@mui/utils";
import { useSelector } from "react-redux";
import {
AbilitySummary,
CampaignSummary,
SavingThrowsSummary,
} from "@dndbeyond/character-components/es";
import {
AbilityManager,
DataOrigin,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { useSidebar } from "~/contexts/Sidebar";
import { useAbilities } from "~/hooks/useAbilities";
import { getDataOriginComponentInfo } from "../../../../../../subApps/sheet/components/Sidebar/helpers/paneUtils";
import { PaneComponentEnum } from "../../../../../../subApps/sheet/components/Sidebar/types";
import {
appEnvSelectors,
characterRollContextSelectors,
} from "../../../../Shared/selectors";
import { PaneIdentifierUtils } from "../../../../Shared/utils";
import MobileDivider from "../../../components/MobileDivider";
import SavingThrowsDetails from "../../../components/SavingThrowsDetails";
import Senses from "../../../components/Senses";
import SubsectionMobile from "../../../components/SubsectionMobile";
export default function MainMobile() {
const abilities = useAbilities();
const {
pane: { paneHistoryStart, paneHistoryPush },
} = useSidebar();
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 ruleData = useSelector(rulesEngineSelectors.getRuleData);
const campaign = useSelector(rulesEngineSelectors.getCampaign);
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
const deathSaveInfo = useSelector(rulesEngineSelectors.getDeathSaveInfo);
const diceEnabled = useSelector(appEnvSelectors.getDiceEnabled);
const characterRollContext = useSelector(
characterRollContextSelectors.getCharacterRollContext
);
const gameLog = useSelector(appEnvSelectors.getGameLog);
const handleSensesManageShow = (): void => {
paneHistoryStart(PaneComponentEnum.SENSE_MANAGE);
};
const handleSavingThrowsClick = (): void => {
paneHistoryStart(PaneComponentEnum.SAVING_THROWS);
};
const handleAbilitySummaryClick = (ability: AbilityManager): void => {
paneHistoryStart(
PaneComponentEnum.ABILITY,
PaneIdentifierUtils.generateAbility(ability)
);
};
const handleAbilityClick = (ability: AbilityManager): void => {
paneHistoryStart(
PaneComponentEnum.ABILITY_SAVING_THROW,
PaneIdentifierUtils.generateAbilitySavingThrows(ability.getId())
);
};
const handleDataOriginClick = (dataOrigin: DataOrigin): void => {
let component = getDataOriginComponentInfo(dataOrigin);
if (component.type !== PaneComponentEnum.ERROR_404) {
paneHistoryPush(component.type, component.identifiers);
}
};
const handleCampaignShow = (): void => {
paneHistoryStart(PaneComponentEnum.CAMPAIGN);
};
return (
<SubsectionMobile name="Main">
<div
className={`ct-main-mobile ${
theme.isDarkMode ? "ct-main-mobile--dark-mode" : ""
}`}
>
{campaign && (
<CampaignSummary
campaign={campaign}
onCampaignShow={handleCampaignShow}
className={
theme.isDarkMode
? "ct-main-mobile__campaign-button--dark-mode"
: ""
}
theme={theme}
gameLog={gameLog}
/>
)}
<section className="ct-main-mobile__abilities">
<h2 style={visuallyHidden}>Ability Scores</h2>
{abilities.map((ability) => (
<div className="ct-main-mobile__ability" key={ability.getStatKey()}>
<AbilitySummary
ability={ability}
preferences={preferences}
theme={theme}
onClick={handleAbilitySummaryClick}
diceEnabled={diceEnabled}
rollContext={characterRollContext}
/>
</div>
))}
</section>
<MobileDivider
label="Saving Throws"
onClick={handleSavingThrowsClick}
theme={theme}
/>
<section
className={`ct-main-mobile__saving-throws ${
theme.isDarkMode ? "ct-main-mobile__saving-throws--dark-mode" : ""
}`}
>
<h2 style={visuallyHidden}>Saving Throws</h2>
<SavingThrowsSummary
abilities={abilities}
situationalBonusSavingThrowsLookup={
situationalBonusSavingThrowsLookup
}
onClick={handleAbilityClick}
diceEnabled={diceEnabled}
theme={theme}
rollContext={characterRollContext}
/>
<SavingThrowsDetails
theme={theme}
ruleData={ruleData}
savingThrowDiceAdjustments={savingThrowDiceAdjustments}
onDataOriginClick={handleDataOriginClick}
deathSaveInfo={deathSaveInfo}
/>
</section>
<MobileDivider isEnd={true} theme={theme} />
<MobileDivider
label="Senses"
onClick={handleSensesManageShow}
theme={theme}
/>
<h2 style={visuallyHidden}>Senses</h2>
<Senses
senses={senses}
theme={theme}
passiveInsight={Number(passiveInsight)}
passiveInvestigation={Number(passiveInvestigation)}
passivePerception={Number(passivePerception)}
onClick={handleSensesManageShow}
/>
<MobileDivider isEnd={true} theme={theme} />
</div>
</SubsectionMobile>
);
}
@@ -0,0 +1,4 @@
import MainMobile from "./MainMobile";
export default MainMobile;
export { MainMobile };
@@ -0,0 +1,41 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect, DispatchProp } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import Notes from "../../Notes";
interface Props extends DispatchProp {
theme: CharacterTheme;
}
class NotesMobile extends React.PureComponent<Props> {
render() {
const { theme } = this.props;
return (
<SubsectionMobile className="ct-notes-mobile">
<MobileDivider label="Notes" theme={theme} />
<section>
<h2 style={visuallyHidden}>Notes</h2>
<Notes />
</section>
<MobileDivider isEnd={true} theme={theme} />
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
export default connect(mapStateToProps)(NotesMobile);
@@ -0,0 +1,4 @@
import NotesMobile from "./NotesMobile";
export default NotesMobile;
export { NotesMobile };
@@ -0,0 +1,47 @@
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 { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { appEnvSelectors } from "../../../../Shared/selectors";
import MobileDivider from "../../../components/MobileDivider";
import ProficiencyGroups from "../../../components/ProficiencyGroups";
import SubsectionMobile from "../../../components/SubsectionMobile";
interface Props {}
export const ProficiencyGroupsMobile: FC<Props> = () => {
const {
pane: { paneHistoryStart },
} = useSidebar();
const { proficiencyGroups, characterTheme } = useCharacterEngine();
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
const handleManageShow = (): void => {
if (!isReadonly) {
paneHistoryStart(PaneComponentEnum.PROFICIENCIES);
}
};
return (
<SubsectionMobile name="Proficiency Groups">
<MobileDivider
label={"Proficiencies & Training"}
onClick={handleManageShow}
isReadonly={isReadonly}
theme={characterTheme}
/>
<section className="ct-proficiency-groups-mobile">
<h2 style={visuallyHidden}>Proficiencies and Training</h2>
<ProficiencyGroups
proficiencyGroups={proficiencyGroups}
onClick={handleManageShow}
/>
</section>
<MobileDivider isEnd={true} theme={characterTheme} />
</SubsectionMobile>
);
};
@@ -0,0 +1,131 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect } from "react-redux";
import {
rulesEngineSelectors,
Skill,
SkillUtils,
ValueLookup,
CharacterTheme,
RuleData,
} from "@dndbeyond/character-rules-engine/es";
import { IRollContext } from "@dndbeyond/dice";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import {
appEnvSelectors,
characterRollContextSelectors,
} from "../../../../Shared/selectors";
import { PaneIdentifierUtils } from "../../../../Shared/utils";
import MobileDivider from "../../../components/MobileDivider";
import Skills from "../../../components/Skills";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
interface Props {
skills: Array<Skill>;
customSkills: Array<Skill>;
valueLookup: ValueLookup;
theme: CharacterTheme;
diceEnabled: boolean;
ruleData: RuleData;
characterRollContext: IRollContext;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class SkillsBox extends React.PureComponent<Props> {
handleManageShow = (): void => {
const { paneHistoryStart } = this.props;
paneHistoryStart(PaneComponentEnum.SKILLS);
};
handleCustomSkillClick = (skill: Skill): void => {
const { paneHistoryStart } = this.props;
paneHistoryStart(
PaneComponentEnum.CUSTOM_SKILL,
PaneIdentifierUtils.generateCustomSkill(SkillUtils.getId(skill))
);
};
handleEmptyClick = (): void => {
this.handleManageShow();
};
handleSkillClick = (skill: Skill): void => {
const { paneHistoryStart } = this.props;
paneHistoryStart(
PaneComponentEnum.SKILL,
PaneIdentifierUtils.generateSkill(SkillUtils.getId(skill))
);
};
render() {
const {
skills,
valueLookup,
customSkills,
theme,
diceEnabled,
ruleData,
characterRollContext,
} = this.props;
return (
<SubsectionMobile name="Skills">
<section
className={`ct-skills-mobile ${
theme.isDarkMode ? "ct-skills-mobile--dark-mode" : ""
}`}
>
<MobileDivider
label="Skills"
onClick={this.handleManageShow}
theme={theme}
/>
<h2 style={visuallyHidden}>Skills</h2>
<Skills
skills={skills}
customSkills={customSkills}
valueLookup={valueLookup}
onCustomSkillClick={this.handleCustomSkillClick}
onSkillClick={this.handleSkillClick}
onEmptyClick={this.handleEmptyClick}
diceEnabled={diceEnabled}
ruleData={ruleData}
theme={theme}
rollContext={characterRollContext}
/>
<MobileDivider isEnd={true} theme={theme} />
</section>
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
skills: rulesEngineSelectors.getSkills(state),
valueLookup: rulesEngineSelectors.getCharacterValueLookup(state),
customSkills: rulesEngineSelectors.getCustomSkills(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
diceEnabled: appEnvSelectors.getDiceEnabled(state),
ruleData: rulesEngineSelectors.getRuleData(state),
characterRollContext:
characterRollContextSelectors.getCharacterRollContext(state),
};
}
const SkillsBoxContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <SkillsBox {...props} paneHistoryStart={paneHistoryStart} />;
};
export default connect(mapStateToProps)(SkillsBoxContainer);
@@ -0,0 +1,4 @@
import SkillsMobile from "./SkillsMobile";
export default SkillsMobile;
export { SkillsMobile };
@@ -0,0 +1,68 @@
import { visuallyHidden } from "@mui/utils";
import React from "react";
import { connect } from "react-redux";
import {
CharacterTheme,
rulesEngineSelectors,
} from "@dndbeyond/character-rules-engine/es";
import { PaneInfo, useSidebar } from "~/contexts/Sidebar/Sidebar";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
import { appEnvSelectors } from "../../../../Shared/selectors";
import MobileDivider from "../../../components/MobileDivider";
import SubsectionMobile from "../../../components/SubsectionMobile";
import { SheetAppState } from "../../../typings";
import Spells from "../../Spells";
interface Props {
isReadonly: boolean;
theme: CharacterTheme;
paneHistoryStart: PaneInfo["paneHistoryStart"];
}
class SpellsMobile extends React.PureComponent<Props> {
handleManageSpellsOpen = (): void => {
const { paneHistoryStart, isReadonly } = this.props;
if (!isReadonly) {
paneHistoryStart(PaneComponentEnum.SPELL_MANAGE);
}
};
render() {
const { isReadonly, theme } = this.props;
return (
<SubsectionMobile>
<MobileDivider
label="Spells"
onClick={this.handleManageSpellsOpen}
isReadonly={isReadonly}
theme={theme}
/>
<section className="ct-spells-mobile">
<h2 style={visuallyHidden}>Spells</h2>
<Spells showNotes={false} />
</section>
<MobileDivider isEnd={true} theme={theme} />
</SubsectionMobile>
);
}
}
function mapStateToProps(state: SheetAppState) {
return {
isReadonly: appEnvSelectors.getIsReadonly(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
const SpellsMobileContainer = (props) => {
const {
pane: { paneHistoryStart },
} = useSidebar();
return <SpellsMobile {...props} paneHistoryStart={paneHistoryStart} />;
};
export default connect(mapStateToProps)(SpellsMobileContainer);
@@ -0,0 +1,4 @@
import SpellsMobile from "./SpellsMobile";
export default SpellsMobile;
export { SpellsMobile };