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:
+105
@@ -0,0 +1,105 @@
|
||||
import { FC, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { FeatureManager } from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { ThemeButton } from "~/tools/js/Shared/components/common/Button";
|
||||
import DataLoadingStatusEnum from "~/tools/js/Shared/constants/DataLoadingStatusEnum";
|
||||
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
|
||||
import { AppLoggerUtils, AppNotificationUtils } from "~/tools/js/Shared/utils";
|
||||
import Collapsible, {
|
||||
CollapsibleHeaderContent,
|
||||
} from "~/tools/js/smartComponents/Collapsible";
|
||||
|
||||
export const BlessingShoppe: FC<{}> = () => {
|
||||
const { characterFeaturesManager } = useContext(
|
||||
CharacterFeaturesManagerContext
|
||||
);
|
||||
|
||||
const [loadingStatus, setLoadingStatus] = useState<DataLoadingStatusEnum>(
|
||||
DataLoadingStatusEnum.NOT_INITIALIZED
|
||||
);
|
||||
const [blessingShoppe, setBlessingShoppe] = useState<Array<FeatureManager>>(
|
||||
[]
|
||||
);
|
||||
const [blessings, setBlessings] = useState<Array<FeatureManager>>([]);
|
||||
|
||||
async function fetchBlessings() {
|
||||
const blessings = await characterFeaturesManager.getBlessings();
|
||||
setBlessings(blessings);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingStatus !== DataLoadingStatusEnum.LOADED) {
|
||||
characterFeaturesManager
|
||||
.getBlessingShoppe()
|
||||
.then((blessingShoppe) => {
|
||||
setLoadingStatus(DataLoadingStatusEnum.LOADED);
|
||||
setBlessingShoppe(blessingShoppe);
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
}
|
||||
fetchBlessings();
|
||||
}, [loadingStatus, characterFeaturesManager]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
A blessing is usually bestowed by a god or a godlike being. A character
|
||||
might receive a blessing from a deity for doing something truly
|
||||
momentous — an accomplishment that catches the attention of both gods
|
||||
and mortals.
|
||||
</p>
|
||||
<div>
|
||||
{blessingShoppe.map((blessing) => {
|
||||
return (
|
||||
<Collapsible
|
||||
key={blessing.getId()}
|
||||
layoutType={"minimal"}
|
||||
header={
|
||||
<CollapsibleHeaderContent
|
||||
heading={blessing.getName()}
|
||||
callout={
|
||||
characterFeaturesManager.hasBlessing(blessing) ? (
|
||||
<ThemeButton
|
||||
onClick={() => {
|
||||
blessing.handleRemove(() => {
|
||||
fetchBlessings();
|
||||
// AppNotificationUtils.dispatchSuccess(
|
||||
// 'Removed Blessing',
|
||||
// 'You removed a blessing',
|
||||
// );
|
||||
});
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
Remove
|
||||
</ThemeButton>
|
||||
) : (
|
||||
<ThemeButton
|
||||
onClick={() => {
|
||||
blessing.handleAdd(() => {
|
||||
fetchBlessings();
|
||||
AppNotificationUtils.dispatchSuccess(
|
||||
"Added Blessing",
|
||||
"You added a blessing"
|
||||
);
|
||||
});
|
||||
}}
|
||||
size="small"
|
||||
style="outline"
|
||||
>
|
||||
Add
|
||||
</ThemeButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{blessing.getDescription()}
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import clsx from "clsx";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
import { FeatManager } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Reference } from "~/components/Reference";
|
||||
import { DataOriginTypeEnum } from "~/constants";
|
||||
import { useCharacterTheme } from "~/contexts/CharacterTheme";
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import Collapsible, {
|
||||
CollapsibleHeaderContent,
|
||||
} from "~/tools/js/smartComponents/Collapsible";
|
||||
import { TodoIcon } from "~/tools/js/smartComponents/Icons";
|
||||
import PrerequisiteFailureSummary from "~/tools/js/smartComponents/PrerequisiteFailureSummary";
|
||||
import { CharacterTheme } from "~/types";
|
||||
|
||||
import {
|
||||
RemoveButton,
|
||||
ThemeButton,
|
||||
} from "../../../../../../../tools/js/Shared/components/common/Button";
|
||||
import { FeatDetail } from "../FeatDetail";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props {
|
||||
feat: FeatManager;
|
||||
enableRemove?: boolean;
|
||||
enableAdd?: boolean;
|
||||
showFailures?: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
|
||||
export const Feat: FC<Props> = ({
|
||||
feat,
|
||||
enableAdd = true,
|
||||
enableRemove = true,
|
||||
showFailures = false,
|
||||
theme,
|
||||
}) => {
|
||||
const { entityUtils } = useCharacterEngine();
|
||||
const { isDarkMode } = useCharacterTheme();
|
||||
|
||||
let missingChoiceCount: number = feat.getUnfinishedChoices().length;
|
||||
let calloutNode: ReactNode = null;
|
||||
let dataOrigin = feat.getDataOrigin();
|
||||
let dataOriginType = feat.getDataOriginType();
|
||||
|
||||
switch (dataOriginType) {
|
||||
case DataOriginTypeEnum.ADHOC:
|
||||
if (enableRemove) {
|
||||
calloutNode = (
|
||||
<RemoveButton
|
||||
onClick={feat.handleRemove}
|
||||
style="filled"
|
||||
className={styles.button}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case DataOriginTypeEnum.SIMULATED:
|
||||
if (enableAdd) {
|
||||
calloutNode = (
|
||||
<ThemeButton
|
||||
onClick={feat.handleAdd}
|
||||
size="small"
|
||||
style="outline"
|
||||
className={styles.button}
|
||||
>
|
||||
Add
|
||||
</ThemeButton>
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
const dataOriginExtra = entityUtils.getDataOriginName(
|
||||
dataOrigin,
|
||||
"Unknown",
|
||||
true
|
||||
);
|
||||
|
||||
calloutNode = (
|
||||
<span className={styles.origin}>
|
||||
<span className={styles.originLabel}>From</span>
|
||||
<span className={styles.originName}>{dataOriginExtra}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
layoutType={"minimal"}
|
||||
className={styles.feat}
|
||||
header={
|
||||
<CollapsibleHeaderContent
|
||||
heading={
|
||||
<div>
|
||||
<div>
|
||||
<span>{feat.getName()}</span>
|
||||
{missingChoiceCount > 0 && (
|
||||
<TodoIcon
|
||||
theme={theme}
|
||||
title={`Choice${
|
||||
missingChoiceCount !== 1 ? "s" : ""
|
||||
} Needed`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className={styles.source}>
|
||||
<Reference name={feat.getPrimarySourceName()} />
|
||||
</span>
|
||||
{showFailures && (
|
||||
<PrerequisiteFailureSummary
|
||||
failures={feat.getPrerequisiteFailures()}
|
||||
className={styles.failures}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
callout={calloutNode}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FeatDetail
|
||||
featManager={feat}
|
||||
className={clsx([styles.detail, isDarkMode && styles.dark])}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { FC, HTMLAttributes } from "react";
|
||||
|
||||
import { FeatManager } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
import { DetailChoiceFeat } from "~/tools/js/Shared/containers/DetailChoice";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
featManager: FeatManager;
|
||||
}
|
||||
|
||||
export const FeatDetail: FC<Props> = ({ featManager, className, ...props }) => {
|
||||
const prerequisiteDescription = featManager.getPrerequisiteDescription();
|
||||
const featDescription = featManager.getDescription();
|
||||
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
{prerequisiteDescription && (
|
||||
<div className={styles.prereq}>
|
||||
Prerequisite: {prerequisiteDescription}
|
||||
</div>
|
||||
)}
|
||||
{featDescription && (
|
||||
<HtmlContent html={featDescription} withoutTooltips />
|
||||
)}
|
||||
{featManager.getChoices().length > 0 && (
|
||||
<div className={styles.choices}>
|
||||
<DetailChoiceFeat featId={featManager.getId()} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { FC, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { FeatManager } from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { useSource } from "~/hooks/useSource";
|
||||
import DataLoadingStatusEnum from "~/tools/js/Shared/constants/DataLoadingStatusEnum";
|
||||
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
|
||||
import { AppLoggerUtils } from "~/tools/js/Shared/utils";
|
||||
import { isNotNullOrUndefined } from "~/tools/js/Shared/utils/TypeScript";
|
||||
import Collapsible, {
|
||||
CollapsibleHeaderContent,
|
||||
CollapsibleHeading,
|
||||
} from "~/tools/js/smartComponents/Collapsible";
|
||||
import LoadingPlaceholder from "~/tools/js/smartComponents/LoadingPlaceholder";
|
||||
|
||||
import { Feat } from "../Feat/Feat";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
export const FeatShoppe: FC<{}> = () => {
|
||||
const { characterFeaturesManager } = useContext(
|
||||
CharacterFeaturesManagerContext
|
||||
);
|
||||
|
||||
const {
|
||||
feats: currentCharacterFeats,
|
||||
characterTheme: theme,
|
||||
preferences,
|
||||
} = useCharacterEngine();
|
||||
|
||||
const { getSourceCategoryGroups } = useSource();
|
||||
|
||||
const [loadingStatus, setLoadingStatus] = useState<DataLoadingStatusEnum>(
|
||||
DataLoadingStatusEnum.NOT_INITIALIZED
|
||||
);
|
||||
const [featShoppe, setFeatShoppe] = useState<Array<FeatManager>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingStatus !== DataLoadingStatusEnum.LOADED) {
|
||||
characterFeaturesManager
|
||||
.getFeatShoppe()
|
||||
.then((featShoppe) => {
|
||||
setLoadingStatus(DataLoadingStatusEnum.LOADED);
|
||||
setFeatShoppe(featShoppe);
|
||||
})
|
||||
.catch(AppLoggerUtils.handleAdhocApiError);
|
||||
} else {
|
||||
setFeatShoppe(characterFeaturesManager.updateFeatShoppe(featShoppe));
|
||||
}
|
||||
}, [currentCharacterFeats]);
|
||||
|
||||
const getFeatsInSourceCategoryGroups = (feats: FeatManager[]) => {
|
||||
const featDefs = feats
|
||||
.map((feat) => feat.getDefinition())
|
||||
.filter(isNotNullOrUndefined);
|
||||
|
||||
return getSourceCategoryGroups(featDefs).map((group) => {
|
||||
return {
|
||||
...group,
|
||||
items: characterFeaturesManager.transformLoadedFeats(group.items),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return loadingStatus !== DataLoadingStatusEnum.LOADED ? (
|
||||
<LoadingPlaceholder />
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.featList}>
|
||||
{getFeatsInSourceCategoryGroups(
|
||||
characterFeaturesManager.getAvailableFeats(featShoppe)
|
||||
).map((group) => (
|
||||
<Collapsible
|
||||
key={group.id + "available"}
|
||||
layoutType="minimal"
|
||||
initiallyCollapsed={false}
|
||||
header={<h2>{group.name}</h2>}
|
||||
>
|
||||
<div>
|
||||
{group.items.map((feat) => (
|
||||
<Feat theme={theme} key={feat.getId()} feat={feat} />
|
||||
))}
|
||||
</div>
|
||||
</Collapsible>
|
||||
))}
|
||||
</div>
|
||||
{characterFeaturesManager.getUnavailableFeats(featShoppe).length > 0 && (
|
||||
<Collapsible
|
||||
header={
|
||||
<CollapsibleHeaderContent
|
||||
heading={
|
||||
<CollapsibleHeading>
|
||||
Unavailable Feats
|
||||
<span className={styles.unavailableCallout}>
|
||||
Prerequisites Not Met
|
||||
</span>
|
||||
</CollapsibleHeading>
|
||||
}
|
||||
/>
|
||||
}
|
||||
className={styles.featList}
|
||||
>
|
||||
<>
|
||||
{getFeatsInSourceCategoryGroups(
|
||||
characterFeaturesManager.getUnavailableFeats(featShoppe)
|
||||
).map((group) => (
|
||||
<Collapsible
|
||||
key={group.id + "unavailable"}
|
||||
layoutType="minimal"
|
||||
initiallyCollapsed={false}
|
||||
header={<h2>{group.name}</h2>}
|
||||
>
|
||||
<div>
|
||||
{group.items.map((feat) => (
|
||||
<Feat
|
||||
theme={theme}
|
||||
key={feat.getId()}
|
||||
feat={feat}
|
||||
enableAdd={!preferences?.enforceFeatRules}
|
||||
showFailures={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Collapsible>
|
||||
))}
|
||||
</>
|
||||
</Collapsible>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { FC } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { useFeatureFlags } from "~/contexts/FeatureFlag";
|
||||
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
|
||||
import SettingsButton from "~/tools/js/CharacterSheet/components/SettingsButton";
|
||||
import { SettingsContextsEnum } from "~/tools/js/Shared/containers/panes/SettingsPane/typings";
|
||||
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
|
||||
import Collapsible, {
|
||||
CollapsibleHeaderContent,
|
||||
CollapsibleHeading,
|
||||
} from "~/tools/js/smartComponents/Collapsible";
|
||||
|
||||
import { BlessingShoppe } from "./BlessingShoppe";
|
||||
import { FeatShoppe } from "./FeatShoppe";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export const FeatsManagePane: FC<{}> = () => {
|
||||
const { gfsBlessingsUiFlag } = useFeatureFlags();
|
||||
|
||||
const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header
|
||||
callout={
|
||||
<SettingsButton
|
||||
context={SettingsContextsEnum.FEATURES}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Manage {gfsBlessingsUiFlag ? "Features" : "Feats"}
|
||||
</Header>
|
||||
{gfsBlessingsUiFlag ? (
|
||||
<Collapsible
|
||||
header={
|
||||
<CollapsibleHeaderContent
|
||||
heading={<CollapsibleHeading>Add Feats</CollapsibleHeading>}
|
||||
/>
|
||||
}
|
||||
className={styles.featList}
|
||||
>
|
||||
<FeatShoppe />
|
||||
</Collapsible>
|
||||
) : (
|
||||
<FeatShoppe />
|
||||
)}
|
||||
{gfsBlessingsUiFlag && (
|
||||
<Collapsible
|
||||
header={
|
||||
<CollapsibleHeaderContent
|
||||
heading={<CollapsibleHeading>Add Blessings</CollapsibleHeading>}
|
||||
/>
|
||||
}
|
||||
className={styles.featList}
|
||||
>
|
||||
<BlessingShoppe />
|
||||
</Collapsible>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user