New source found from dndbeyond.com
This commit is contained in:
+12
-261
@@ -1,11 +1,8 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterPromise,
|
||||
ApiAdapterRequestConfig,
|
||||
ApiAdapterUtils,
|
||||
ApiResponse,
|
||||
CharacterPreferences,
|
||||
CharClass,
|
||||
@@ -18,10 +15,8 @@ import {
|
||||
Constants,
|
||||
DefinitionPool,
|
||||
EntitledEntity,
|
||||
Feat,
|
||||
FeatDefinitionContract,
|
||||
FeatLookup,
|
||||
FeatUtils,
|
||||
FormatUtils,
|
||||
HelperUtils,
|
||||
InfusionChoice,
|
||||
@@ -35,20 +30,16 @@ import {
|
||||
OptionalClassFeatureUtils,
|
||||
PrerequisiteData,
|
||||
RuleData,
|
||||
RuleDataUtils,
|
||||
SourceData,
|
||||
TypeValueLookup,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Accordion } from "~/components/Accordion";
|
||||
import { CollapsibleContent } from "~/components/CollapsibleContent";
|
||||
import { FeatureChoice } from "~/components/FeatureChoice";
|
||||
import { FeatureChoices } from "~/components/FeatureChoices";
|
||||
import { Link } from "~/components/Link";
|
||||
import { GrantedFeat } from "~/tools/js/CharacterBuilder/components/GrantedFeat/GrantedFeat";
|
||||
|
||||
import InfusionChoiceManager from "../../../../../Shared/components/InfusionChoiceManager";
|
||||
import DataLoadingStatusEnum from "../../../../../Shared/constants/DataLoadingStatusEnum";
|
||||
import { AppLoggerUtils, TypeScriptUtils } from "../../../../../Shared/utils";
|
||||
|
||||
interface Props {
|
||||
isActive: boolean;
|
||||
@@ -122,13 +113,7 @@ interface Props {
|
||||
inventoryManager: InventoryManager;
|
||||
}
|
||||
interface State {
|
||||
featData: Array<Feat>;
|
||||
subclassData: Array<ClassDefinitionContract>;
|
||||
hasFeatChoice: boolean;
|
||||
hasSubclassChoice: boolean;
|
||||
collapsibleOpened: boolean;
|
||||
featLoadingStatus: DataLoadingStatusEnum;
|
||||
subclassLoadingStatus: DataLoadingStatusEnum;
|
||||
}
|
||||
export default class ClassManagerFeature extends React.PureComponent<
|
||||
Props,
|
||||
@@ -139,157 +124,16 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
featLookup: {},
|
||||
};
|
||||
|
||||
loadFeatsCanceler: null | Canceler = null;
|
||||
loadSubclassesCanceler: null | Canceler = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
featData: [],
|
||||
subclassData: [],
|
||||
hasFeatChoice: false,
|
||||
hasSubclassChoice: false,
|
||||
collapsibleOpened: false,
|
||||
featLoadingStatus: DataLoadingStatusEnum.NOT_INITIALIZED,
|
||||
subclassLoadingStatus: DataLoadingStatusEnum.NOT_INITIALIZED,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setState(
|
||||
{
|
||||
hasFeatChoice: this.hasFeatChoice(this.props),
|
||||
hasSubclassChoice: this.hasSubclassChoice(this.props),
|
||||
},
|
||||
() => {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<Props>,
|
||||
prevState: Readonly<State>,
|
||||
snapshot?: any
|
||||
): void {
|
||||
hasSubclassChoice = (): boolean => {
|
||||
const { feature } = this.props;
|
||||
const { collapsibleOpened } = this.state;
|
||||
|
||||
if (feature !== prevProps.feature) {
|
||||
this.setState(
|
||||
{
|
||||
hasFeatChoice: this.hasFeatChoice(this.props),
|
||||
hasSubclassChoice: this.hasSubclassChoice(this.props),
|
||||
},
|
||||
() => {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
);
|
||||
}
|
||||
if (collapsibleOpened && !prevState.collapsibleOpened) {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
}
|
||||
|
||||
conditionallyLoadFeatData = (): void => {
|
||||
const { loadAvailableFeats, loadAvailableSubclasses, charClass } =
|
||||
this.props;
|
||||
const {
|
||||
hasFeatChoice,
|
||||
hasSubclassChoice,
|
||||
featLoadingStatus,
|
||||
subclassLoadingStatus,
|
||||
collapsibleOpened,
|
||||
} = this.state;
|
||||
|
||||
if (
|
||||
hasFeatChoice &&
|
||||
collapsibleOpened &&
|
||||
featLoadingStatus === DataLoadingStatusEnum.NOT_INITIALIZED
|
||||
) {
|
||||
this.setState({
|
||||
featLoadingStatus: DataLoadingStatusEnum.LOADING,
|
||||
});
|
||||
|
||||
loadAvailableFeats({
|
||||
cancelToken: new axios.CancelToken((c) => {
|
||||
this.loadFeatsCanceler = c;
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
let featData: Array<Feat> = [];
|
||||
|
||||
const data = ApiAdapterUtils.getResponseData(response);
|
||||
if (data !== null) {
|
||||
featData = data.map((definition) =>
|
||||
FeatUtils.simulateFeat(definition)
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
featData,
|
||||
featLoadingStatus: DataLoadingStatusEnum.LOADED,
|
||||
});
|
||||
this.loadFeatsCanceler = null;
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
}
|
||||
|
||||
if (
|
||||
hasSubclassChoice &&
|
||||
collapsibleOpened &&
|
||||
subclassLoadingStatus === DataLoadingStatusEnum.NOT_INITIALIZED
|
||||
) {
|
||||
this.setState({
|
||||
subclassLoadingStatus: DataLoadingStatusEnum.LOADING,
|
||||
});
|
||||
|
||||
loadAvailableSubclasses(ClassUtils.getId(charClass), {
|
||||
cancelToken: new axios.CancelToken((c) => {
|
||||
this.loadSubclassesCanceler = c;
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
let subclassData: Array<ClassDefinitionContract> = [];
|
||||
|
||||
const data = ApiAdapterUtils.getResponseData(response);
|
||||
if (data !== null) {
|
||||
subclassData = data;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
subclassData,
|
||||
subclassLoadingStatus: DataLoadingStatusEnum.LOADED,
|
||||
});
|
||||
this.loadSubclassesCanceler = null;
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.loadFeatsCanceler !== null) {
|
||||
this.loadFeatsCanceler();
|
||||
}
|
||||
if (this.loadSubclassesCanceler !== null) {
|
||||
this.loadSubclassesCanceler();
|
||||
}
|
||||
}
|
||||
|
||||
hasFeatChoice = (props: Props): boolean => {
|
||||
const { feature } = props;
|
||||
|
||||
const choices = ClassFeatureUtils.getChoices(feature);
|
||||
return choices.some(
|
||||
(choice) =>
|
||||
ChoiceUtils.getType(choice) ===
|
||||
Constants.BuilderChoiceTypeEnum.FEAT_CHOICE_OPTION
|
||||
);
|
||||
};
|
||||
|
||||
hasSubclassChoice = (props: Props): boolean => {
|
||||
const { feature } = props;
|
||||
|
||||
const choices = ClassFeatureUtils.getChoices(feature);
|
||||
return choices.some(
|
||||
@@ -299,28 +143,6 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
);
|
||||
};
|
||||
|
||||
isDataLoaded = (): boolean => {
|
||||
const {
|
||||
hasSubclassChoice,
|
||||
hasFeatChoice,
|
||||
subclassLoadingStatus,
|
||||
featLoadingStatus,
|
||||
} = this.state;
|
||||
|
||||
if (hasSubclassChoice) {
|
||||
if (subclassLoadingStatus !== DataLoadingStatusEnum.LOADED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasFeatChoice) {
|
||||
if (featLoadingStatus !== DataLoadingStatusEnum.LOADED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
handleCollapsibleOpened = (id: string, state: boolean): void => {
|
||||
this.setState({
|
||||
collapsibleOpened: state,
|
||||
@@ -436,46 +258,6 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
);
|
||||
};
|
||||
|
||||
getSubclassSources = (
|
||||
subclass: ClassDefinitionContract
|
||||
): Array<SourceData> => {
|
||||
const { ruleData } = this.props;
|
||||
|
||||
if (subclass.sources === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return subclass.sources
|
||||
.map((sourceMapping) =>
|
||||
HelperUtils.lookupDataOrFallback(
|
||||
RuleDataUtils.getSourceDataLookup(ruleData),
|
||||
sourceMapping.sourceId
|
||||
)
|
||||
)
|
||||
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
||||
};
|
||||
|
||||
getSubclassData = (): Array<ClassDefinitionContract> => {
|
||||
const { subclassData } = this.state;
|
||||
const { charClass } = this.props;
|
||||
|
||||
let data: Array<ClassDefinitionContract> = [...subclassData];
|
||||
|
||||
let existingSubclass = ClassUtils.getSubclass(charClass);
|
||||
if (
|
||||
existingSubclass !== null &&
|
||||
!data.some(
|
||||
(classDefinition) =>
|
||||
existingSubclass !== null &&
|
||||
classDefinition.id === existingSubclass.id
|
||||
)
|
||||
) {
|
||||
data.push(existingSubclass);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
getClassFeatureDescription = (): string => {
|
||||
const { feature, isStartingClass } = this.props;
|
||||
|
||||
@@ -493,33 +275,6 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
return description === null ? "" : description;
|
||||
};
|
||||
|
||||
renderChoices = (): React.ReactNode => {
|
||||
const { isActive, feature, charClass } = this.props;
|
||||
const { featData } = this.state;
|
||||
|
||||
if (!isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const choices = ClassFeatureUtils.getChoices(feature);
|
||||
|
||||
if (choices === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return choices.map((choice) => (
|
||||
<FeatureChoice
|
||||
choice={choice}
|
||||
charClass={charClass}
|
||||
feature={feature}
|
||||
featsData={featData}
|
||||
subclassData={this.getSubclassData()}
|
||||
onChoiceChange={this.handleChoiceChange}
|
||||
key={ChoiceUtils.getId(choice)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
getMetaItems = () => {
|
||||
const { feature, optionalClassFeatureLookup, definitionPool } = this.props;
|
||||
const choiceCount = this.getTotalChoiceCount();
|
||||
@@ -590,7 +345,7 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
feature,
|
||||
inventoryManager,
|
||||
} = this.props;
|
||||
const { hasSubclassChoice, hasFeatChoice } = this.state;
|
||||
const { collapsibleOpened } = this.state;
|
||||
|
||||
// If the class feature grants feats, render those instead of the class feature.
|
||||
if (feature.featLists.length > 0) {
|
||||
@@ -604,22 +359,11 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
}
|
||||
|
||||
const conClsNames: Array<string> = ["class-manager-feature-name"];
|
||||
let contentNode: React.ReactNode;
|
||||
|
||||
if (isActive) {
|
||||
if (this.getTotalTodoCount() > 0) {
|
||||
conClsNames.push("collapsible-todo");
|
||||
}
|
||||
|
||||
if (hasFeatChoice || hasSubclassChoice) {
|
||||
if (this.isDataLoaded()) {
|
||||
contentNode = this.renderChoices();
|
||||
} else {
|
||||
contentNode = <LoadingPlaceholder />;
|
||||
}
|
||||
} else {
|
||||
contentNode = this.renderChoices();
|
||||
}
|
||||
}
|
||||
const hasChoices = this.getTotalTodoCount() > 0;
|
||||
const accordionId = ClassFeatureUtils.getId(feature).toString();
|
||||
@@ -636,13 +380,20 @@ export default class ClassManagerFeature extends React.PureComponent<
|
||||
<CollapsibleContent className="class-manager-feature-description">
|
||||
{this.getClassFeatureDescription()}
|
||||
</CollapsibleContent>
|
||||
{isActive && hasSubclassChoice && (
|
||||
{isActive && this.hasSubclassChoice() && (
|
||||
<div className="ct-character-tools__marketplace-callout">
|
||||
Looking for something not in the list below? Unlock all official
|
||||
options in the <Link href="/marketplace">Marketplace</Link>.
|
||||
</div>
|
||||
)}
|
||||
{contentNode}
|
||||
{isActive && (
|
||||
<FeatureChoices
|
||||
choices={ClassFeatureUtils.getChoices(feature)}
|
||||
charClass={charClass}
|
||||
onChoiceChange={this.handleChoiceChange}
|
||||
shouldFetch={collapsibleOpened}
|
||||
/>
|
||||
)}
|
||||
{isActive && (
|
||||
<InfusionChoiceManager
|
||||
infusionChoices={this.getAvailableInfusionChoices()}
|
||||
|
||||
-1
@@ -585,7 +585,6 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
||||
handleChoiceChange = (
|
||||
choiceId: string,
|
||||
choiceType: number,
|
||||
choiceSubType: number | null,
|
||||
optionValue: any
|
||||
): void => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
+7
-150
@@ -1,11 +1,8 @@
|
||||
import axios, { Canceler } from "axios";
|
||||
import React from "react";
|
||||
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
ApiAdapterPromise,
|
||||
ApiAdapterRequestConfig,
|
||||
ApiAdapterUtils,
|
||||
ApiResponse,
|
||||
CharacterPreferences,
|
||||
ChoiceData,
|
||||
@@ -13,10 +10,8 @@ import {
|
||||
Constants,
|
||||
CoreUtils,
|
||||
DefinitionPool,
|
||||
Feat,
|
||||
FeatDefinitionContract,
|
||||
FeatLookup,
|
||||
FeatUtils,
|
||||
HelperUtils,
|
||||
OptionalOriginLookup,
|
||||
OptionalOriginUtils,
|
||||
@@ -26,12 +21,9 @@ import {
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Accordion } from "~/components/Accordion";
|
||||
import { FeatureChoice } from "~/components/FeatureChoice";
|
||||
import { FeatureChoices } from "~/components/FeatureChoices";
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
|
||||
import DataLoadingStatusEnum from "../../../../../Shared/constants/DataLoadingStatusEnum";
|
||||
import { AppLoggerUtils } from "../../../../../Shared/utils";
|
||||
|
||||
interface Props {
|
||||
speciesTrait: RacialTrait;
|
||||
choiceInfo: ChoiceData;
|
||||
@@ -52,10 +44,7 @@ interface Props {
|
||||
speciesName?: string | null;
|
||||
}
|
||||
interface State {
|
||||
featData: Array<Feat>;
|
||||
hasFeatChoice: boolean;
|
||||
collapsibleOpened: boolean;
|
||||
loadingStatus: DataLoadingStatusEnum;
|
||||
}
|
||||
export default class SpeciesManageSpeciesTrait extends React.PureComponent<
|
||||
Props,
|
||||
@@ -65,115 +54,10 @@ export default class SpeciesManageSpeciesTrait extends React.PureComponent<
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
featData: [],
|
||||
hasFeatChoice: false,
|
||||
collapsibleOpened: false,
|
||||
loadingStatus: DataLoadingStatusEnum.NOT_INITIALIZED,
|
||||
};
|
||||
}
|
||||
|
||||
loadFeatsCanceler: null | Canceler = null;
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setState(
|
||||
{
|
||||
hasFeatChoice: this.hasFeatChoice(this.props),
|
||||
},
|
||||
() => {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<Props>,
|
||||
prevState: Readonly<State>,
|
||||
snapshot?: any
|
||||
): void {
|
||||
const { speciesTrait } = this.props;
|
||||
const { collapsibleOpened } = this.state;
|
||||
|
||||
if (speciesTrait !== prevProps.speciesTrait) {
|
||||
this.setState(
|
||||
{
|
||||
hasFeatChoice: this.hasFeatChoice(this.props),
|
||||
},
|
||||
() => {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
);
|
||||
}
|
||||
if (collapsibleOpened && !prevState.collapsibleOpened) {
|
||||
this.conditionallyLoadFeatData();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.loadFeatsCanceler !== null) {
|
||||
this.loadFeatsCanceler();
|
||||
}
|
||||
}
|
||||
|
||||
conditionallyLoadFeatData = (): void => {
|
||||
const { loadAvailableFeats } = this.props;
|
||||
const { hasFeatChoice, loadingStatus, collapsibleOpened } = this.state;
|
||||
|
||||
if (
|
||||
hasFeatChoice &&
|
||||
collapsibleOpened &&
|
||||
loadingStatus === DataLoadingStatusEnum.NOT_INITIALIZED
|
||||
) {
|
||||
this.setState({
|
||||
loadingStatus: DataLoadingStatusEnum.LOADING,
|
||||
});
|
||||
|
||||
loadAvailableFeats({
|
||||
cancelToken: new axios.CancelToken((c) => {
|
||||
this.loadFeatsCanceler = c;
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
let featData: Array<Feat> = [];
|
||||
|
||||
const data = ApiAdapterUtils.getResponseData(response);
|
||||
if (data !== null) {
|
||||
featData = data.map((definition) =>
|
||||
FeatUtils.simulateFeat(definition)
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
featData,
|
||||
loadingStatus: DataLoadingStatusEnum.LOADED,
|
||||
});
|
||||
this.loadFeatsCanceler = null;
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
}
|
||||
};
|
||||
|
||||
getFeatData = (existingFeatId: number | null): Array<Feat> => {
|
||||
const { featData } = this.state;
|
||||
const { featLookup } = this.props;
|
||||
|
||||
let data: Array<Feat> = [...featData];
|
||||
|
||||
if (existingFeatId !== null) {
|
||||
let existingFeat = HelperUtils.lookupDataOrFallback(
|
||||
featLookup,
|
||||
existingFeatId
|
||||
);
|
||||
if (
|
||||
existingFeat !== null &&
|
||||
!data.some((feat) => FeatUtils.getId(feat) === existingFeatId)
|
||||
) {
|
||||
data.push(existingFeat);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
getSummary = () => {
|
||||
const { speciesTrait, speciesName } = this.props;
|
||||
// Get base name for the species trait
|
||||
@@ -277,29 +161,9 @@ export default class SpeciesManageSpeciesTrait extends React.PureComponent<
|
||||
}
|
||||
};
|
||||
|
||||
renderChoices = (): React.ReactNode => {
|
||||
const { speciesTrait } = this.props;
|
||||
const { featData } = this.state;
|
||||
|
||||
const choices = RacialTraitUtils.getChoices(speciesTrait);
|
||||
|
||||
if (choices === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return choices.map((choice) => (
|
||||
<FeatureChoice
|
||||
choice={choice}
|
||||
featsData={featData}
|
||||
onChoiceChange={this.handleChoiceChange}
|
||||
key={ChoiceUtils.getId(choice)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { speciesTrait } = this.props;
|
||||
const { hasFeatChoice, loadingStatus } = this.state;
|
||||
const { collapsibleOpened } = this.state;
|
||||
|
||||
const description = RacialTraitUtils.getDescription(speciesTrait);
|
||||
const choices = RacialTraitUtils.getChoices(speciesTrait);
|
||||
@@ -314,17 +178,6 @@ export default class SpeciesManageSpeciesTrait extends React.PureComponent<
|
||||
conClsNames.push("collapsible-todo");
|
||||
}
|
||||
|
||||
let contentNode: React.ReactNode;
|
||||
if (hasFeatChoice) {
|
||||
if (loadingStatus === DataLoadingStatusEnum.LOADED) {
|
||||
contentNode = this.renderChoices();
|
||||
} else {
|
||||
contentNode = <LoadingPlaceholder />;
|
||||
}
|
||||
} else {
|
||||
contentNode = this.renderChoices();
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
id={accordionId}
|
||||
@@ -339,7 +192,11 @@ export default class SpeciesManageSpeciesTrait extends React.PureComponent<
|
||||
html={description ? description : ""}
|
||||
withoutTooltips
|
||||
/>
|
||||
{contentNode}
|
||||
<FeatureChoices
|
||||
choices={RacialTraitUtils.getChoices(speciesTrait)}
|
||||
onChoiceChange={this.handleChoiceChange}
|
||||
shouldFetch={collapsibleOpened}
|
||||
/>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user