``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
import { visuallyHidden } from "@mui/utils";
|
|
import React, { useContext, useEffect, useState } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
|
|
import {
|
|
rulesEngineSelectors,
|
|
ClassUtils,
|
|
FeatureUtils,
|
|
InfusionChoiceUtils,
|
|
RacialTrait,
|
|
RacialTraitUtils,
|
|
FeaturesManager,
|
|
FeatManager,
|
|
Action,
|
|
ActionUtils,
|
|
Spell,
|
|
SpellUtils,
|
|
} from "@dndbeyond/character-rules-engine/es";
|
|
|
|
import { TabFilter } from "~/components/TabFilter";
|
|
import { useFeatureFlags } from "~/contexts/FeatureFlag";
|
|
import { useSidebar } from "~/contexts/Sidebar";
|
|
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";
|
|
import { SpeciesDetail } from "~/subApps/sheet/components/SpeciesDetail";
|
|
import { ThemeButton } from "~/tools/js/Shared/components/common/Button";
|
|
|
|
import {
|
|
handleActionUseSet,
|
|
handleSpellUseSet,
|
|
} from "../../../../../handlers/commonHandlers";
|
|
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";
|
|
|
|
//TODO we should break this file out into seperate components rather than have them all in one file
|
|
|
|
const SpeciesTraitsGroup: React.FC<{}> = () => {
|
|
const dispatch = useDispatch();
|
|
const { characterFeaturesManager } = useContext(
|
|
CharacterFeaturesManagerContext
|
|
);
|
|
|
|
const {
|
|
pane: { paneHistoryStart },
|
|
} = useSidebar();
|
|
|
|
const snippetData = useSelector(rulesEngineSelectors.getSnippetData);
|
|
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
|
const abilityLookup = useSelector(rulesEngineSelectors.getAbilityLookup);
|
|
const dataOriginRefData = useSelector(
|
|
rulesEngineSelectors.getDataOriginRefData
|
|
);
|
|
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
|
const proficiencyBonus = useSelector(
|
|
rulesEngineSelectors.getProficiencyBonus
|
|
) as number;
|
|
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
|
const speciesTraits = useSelector(
|
|
rulesEngineSelectors.getCurrentLevelRacialTraits
|
|
) as RacialTrait[];
|
|
|
|
const handleFeatClick = (feat: FeatManager): void => {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.FEAT_DETAIL,
|
|
PaneIdentifierUtils.generateFeat(feat.getId())
|
|
);
|
|
};
|
|
|
|
const handleActionClick = (action: Action): void => {
|
|
const mappingId = ActionUtils.getMappingId(action);
|
|
const mappingEntityTypeId = ActionUtils.getMappingEntityTypeId(action);
|
|
|
|
if (mappingId !== null && mappingEntityTypeId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.ACTION,
|
|
PaneIdentifierUtils.generateAction(mappingId, mappingEntityTypeId)
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleSpellDetailClick = (spell: Spell): void => {
|
|
const mappingId = SpellUtils.getMappingId(spell);
|
|
if (mappingId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.CHARACTER_SPELL_DETAIL,
|
|
PaneIdentifierUtils.generateCharacterSpell(mappingId)
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ContentGroup header="Species Traits">
|
|
<SpeciesDetail
|
|
onActionUseSet={(action, uses) =>
|
|
handleActionUseSet(action, uses, dispatch)
|
|
}
|
|
onActionClick={(action) => handleActionClick(action)}
|
|
onSpellClick={(spell) => handleSpellDetailClick(spell)}
|
|
onSpellUseSet={(spell, uses) =>
|
|
handleSpellUseSet(spell, uses, dispatch)
|
|
}
|
|
onFeatureClick={(feature) => {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.SPECIES_TRAIT_DETAIL,
|
|
PaneIdentifierUtils.generateRacialTrait(
|
|
RacialTraitUtils.getId(feature)
|
|
)
|
|
);
|
|
}}
|
|
feats={characterFeaturesManager
|
|
.getFeats()
|
|
.map((manager) => manager.feat)}
|
|
isReadonly={isReadonly}
|
|
snippetData={snippetData}
|
|
ruleData={ruleData}
|
|
abilityLookup={abilityLookup}
|
|
dataOriginRefData={dataOriginRefData}
|
|
proficiencyBonus={proficiencyBonus}
|
|
theme={theme}
|
|
speciesTraits={speciesTraits}
|
|
onFeatClick={(feat) => handleFeatClick(feat)}
|
|
featuresManager={characterFeaturesManager}
|
|
/>
|
|
</ContentGroup>
|
|
);
|
|
};
|
|
|
|
const ClassFeaturesGroup: React.FC<{}> = () => {
|
|
const dispatch = useDispatch();
|
|
const { characterFeaturesManager } = useContext(
|
|
CharacterFeaturesManagerContext
|
|
);
|
|
|
|
const {
|
|
pane: { paneHistoryStart },
|
|
} = useSidebar();
|
|
|
|
const classes = useSelector(rulesEngineSelectors.getClasses);
|
|
const snippetData = useSelector(rulesEngineSelectors.getSnippetData);
|
|
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
|
const abilityLookup = useSelector(rulesEngineSelectors.getAbilityLookup);
|
|
const dataOriginRefData = useSelector(
|
|
rulesEngineSelectors.getDataOriginRefData
|
|
);
|
|
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
|
const proficiencyBonus = useSelector(
|
|
rulesEngineSelectors.getProficiencyBonus
|
|
);
|
|
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
|
|
|
const handleFeatClick = (feat: FeatManager): void => {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.FEAT_DETAIL,
|
|
PaneIdentifierUtils.generateFeat(feat.getId())
|
|
);
|
|
};
|
|
|
|
const handleActionClick = (action: Action): void => {
|
|
const mappingId = ActionUtils.getMappingId(action);
|
|
const mappingEntityTypeId = ActionUtils.getMappingEntityTypeId(action);
|
|
|
|
if (mappingId !== null && mappingEntityTypeId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.ACTION,
|
|
PaneIdentifierUtils.generateAction(mappingId, mappingEntityTypeId)
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleSpellDetailClick = (spell: Spell): void => {
|
|
const mappingId = SpellUtils.getMappingId(spell);
|
|
if (mappingId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.CHARACTER_SPELL_DETAIL,
|
|
PaneIdentifierUtils.generateCharacterSpell(mappingId)
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ContentGroup header="Class Features">
|
|
<ClassesDetail
|
|
classes={classes}
|
|
onActionUseSet={(action, uses) =>
|
|
handleActionUseSet(action, uses, dispatch)
|
|
}
|
|
onActionClick={(action) => handleActionClick(action)}
|
|
onSpellClick={(spell) => handleSpellDetailClick(spell)}
|
|
onSpellUseSet={(spell, uses) =>
|
|
handleSpellUseSet(spell, uses, dispatch)
|
|
}
|
|
onFeatureClick={(feature, charClass) => {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.CLASS_FEATURE_DETAIL,
|
|
PaneIdentifierUtils.generateClassFeature(
|
|
FeatureUtils.getId(feature),
|
|
ClassUtils.getMappingId(charClass)
|
|
)
|
|
);
|
|
}}
|
|
onInfusionChoiceClick={(infusionChoice) => {
|
|
const choiceKey = InfusionChoiceUtils.getKey(infusionChoice);
|
|
if (choiceKey !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.INFUSION_CHOICE,
|
|
PaneIdentifierUtils.generateInfusionChoice(choiceKey)
|
|
);
|
|
}
|
|
}}
|
|
feats={characterFeaturesManager
|
|
.getFeats()
|
|
.map((manager) => manager.feat)}
|
|
isReadonly={isReadonly}
|
|
snippetData={snippetData}
|
|
ruleData={ruleData}
|
|
abilityLookup={abilityLookup}
|
|
dataOriginRefData={dataOriginRefData}
|
|
proficiencyBonus={proficiencyBonus}
|
|
theme={theme}
|
|
onFeatClick={(feat) => handleFeatClick(feat)}
|
|
featuresManager={characterFeaturesManager}
|
|
/>
|
|
</ContentGroup>
|
|
);
|
|
};
|
|
|
|
const FeatsGroup: React.FC<{}> = () => {
|
|
const dispatch = useDispatch();
|
|
const { gfsBlessingsUiFlag } = useFeatureFlags();
|
|
const {
|
|
pane: { paneHistoryStart },
|
|
} = useSidebar();
|
|
|
|
const snippetData = useSelector(rulesEngineSelectors.getSnippetData);
|
|
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
|
const abilityLookup = useSelector(rulesEngineSelectors.getAbilityLookup);
|
|
const dataOriginRefData = useSelector(
|
|
rulesEngineSelectors.getDataOriginRefData
|
|
);
|
|
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
|
const proficiencyBonus = useSelector(
|
|
rulesEngineSelectors.getProficiencyBonus
|
|
);
|
|
const theme = useSelector(rulesEngineSelectors.getCharacterTheme);
|
|
|
|
const handleFeatClick = (feat: FeatManager): void => {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.FEAT_DETAIL,
|
|
PaneIdentifierUtils.generateFeat(feat.getId())
|
|
);
|
|
};
|
|
|
|
const handleActionClick = (action: Action): void => {
|
|
const mappingId = ActionUtils.getMappingId(action);
|
|
const mappingEntityTypeId = ActionUtils.getMappingEntityTypeId(action);
|
|
|
|
if (mappingId !== null && mappingEntityTypeId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.ACTION,
|
|
PaneIdentifierUtils.generateAction(mappingId, mappingEntityTypeId)
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleSpellDetailClick = (spell: Spell): void => {
|
|
const mappingId = SpellUtils.getMappingId(spell);
|
|
if (mappingId !== null) {
|
|
paneHistoryStart(
|
|
PaneComponentEnum.CHARACTER_SPELL_DETAIL,
|
|
PaneIdentifierUtils.generateCharacterSpell(mappingId)
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ContentGroup header="Feats">
|
|
{!isReadonly && (
|
|
<div className="ct-features__management-link">
|
|
<ThemeButton
|
|
onClick={() => paneHistoryStart(PaneComponentEnum.FEATS_MANAGE)}
|
|
style="outline"
|
|
size="medium"
|
|
>
|
|
Manage {gfsBlessingsUiFlag ? "Features" : "Feats"}
|
|
</ThemeButton>
|
|
</div>
|
|
)}
|
|
<FeatsDetail
|
|
onActionUseSet={(action, uses) =>
|
|
handleActionUseSet(action, uses, dispatch)
|
|
}
|
|
onActionClick={(action) => handleActionClick(action)}
|
|
onSpellClick={(spell) => handleSpellDetailClick(spell)}
|
|
onSpellUseSet={(spell, uses) =>
|
|
handleSpellUseSet(spell, uses, dispatch)
|
|
}
|
|
onFeatureClick={(feat) => handleFeatClick(feat)}
|
|
isReadonly={isReadonly}
|
|
snippetData={snippetData}
|
|
ruleData={ruleData}
|
|
abilityLookup={abilityLookup}
|
|
dataOriginRefData={dataOriginRefData}
|
|
proficiencyBonus={proficiencyBonus}
|
|
theme={theme}
|
|
/>
|
|
</ContentGroup>
|
|
);
|
|
};
|
|
|
|
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>
|
|
<TabFilter
|
|
filters={[
|
|
{
|
|
label: "Class Features",
|
|
content: <ClassFeaturesGroup />,
|
|
},
|
|
{
|
|
label: "Species Traits",
|
|
content: <SpeciesTraitsGroup />,
|
|
},
|
|
{
|
|
label: "Feats",
|
|
content: <FeatsGroup />,
|
|
},
|
|
...(gfsBlessingsUiFlag && hasBlessings
|
|
? [{ label: "Blessings", content: <BlessingsGroup /> }]
|
|
: []),
|
|
]}
|
|
/>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default Features;
|