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:
@@ -0,0 +1,264 @@
|
||||
import { sortBy } from "lodash";
|
||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
DataOriginRefData,
|
||||
EntityValueLookup,
|
||||
rulesEngineSelectors,
|
||||
SimpleSourceCategoryContract,
|
||||
SpellManager as SpellManagerType,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { Link } from "~/components/Link";
|
||||
import { SpellFilter } from "~/components/SpellFilter";
|
||||
|
||||
import DataLoadingStatusEnum from "../../constants/DataLoadingStatusEnum";
|
||||
import { SpellsManagerContext } from "../../managers/SpellsManagerContext";
|
||||
import { FilterUtils } from "../../utils";
|
||||
import SpellManagerItem from "./SpellManagerItem";
|
||||
|
||||
interface Props {
|
||||
charClassId: number;
|
||||
characterClassId: number;
|
||||
shouldFetch?: boolean;
|
||||
hasActiveSpells?: boolean;
|
||||
|
||||
isPrepareMaxed: boolean;
|
||||
isCantripsKnownMaxed: boolean;
|
||||
isSpellsKnownMaxed: boolean;
|
||||
addButtonText: string;
|
||||
enableAdd?: boolean;
|
||||
enablePrepare?: boolean;
|
||||
enableUnprepare?: boolean;
|
||||
enableSpellRemove?: boolean;
|
||||
showFilters?: boolean;
|
||||
showExpandedType?: boolean;
|
||||
showCustomize: boolean;
|
||||
buttonActiveStyle?: string;
|
||||
buttonUnprepareText?: string;
|
||||
entityValueLookup: EntityValueLookup;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
|
||||
// TODO: migrate this all into here
|
||||
export default function SpellManagerContainer({
|
||||
showFilters = true,
|
||||
enableAdd = true,
|
||||
enableSpellRemove = true,
|
||||
showExpandedType = true,
|
||||
enablePrepare = true,
|
||||
enableUnprepare = true,
|
||||
shouldFetch = false,
|
||||
hasActiveSpells = false,
|
||||
charClassId,
|
||||
// TODO: get from manager?
|
||||
characterClassId,
|
||||
isPrepareMaxed,
|
||||
isCantripsKnownMaxed,
|
||||
isSpellsKnownMaxed,
|
||||
addButtonText,
|
||||
buttonActiveStyle,
|
||||
buttonUnprepareText,
|
||||
entityValueLookup,
|
||||
dataOriginRefData,
|
||||
showCustomize,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
}: Props) {
|
||||
const ruleData = useSelector(rulesEngineSelectors.getRuleData);
|
||||
const spellCasterInfo = useSelector(rulesEngineSelectors.getSpellCasterInfo);
|
||||
const { spellsManager } = useContext(SpellsManagerContext);
|
||||
|
||||
const [availableSpells, setAvailableSpells] = useState<
|
||||
Array<SpellManagerType>
|
||||
>([]);
|
||||
const [spells, setSpells] = useState<Array<SpellManagerType>>([]);
|
||||
const [sourceCategories, setSourceCategories] = useState<
|
||||
Array<SimpleSourceCategoryContract>
|
||||
>([]);
|
||||
const [loadingStatus, setLoadingStatus] = useState(
|
||||
DataLoadingStatusEnum.LOADING
|
||||
);
|
||||
const [filterLevels, setFilterLevels] = useState<Array<number>>([]);
|
||||
const [filterQuery, setFilterQuery] = useState("");
|
||||
const [filterSourceCategories, setFilterSourceCategories] = useState<
|
||||
Array<number>
|
||||
>([]);
|
||||
|
||||
const getData = useCallback(
|
||||
async function getData() {
|
||||
let classSpellMap = await spellsManager.getSpellShoppe();
|
||||
if (!classSpellMap[charClassId] || shouldFetch) {
|
||||
classSpellMap = await spellsManager.getSpellShoppe(true);
|
||||
}
|
||||
setSpells(
|
||||
hasActiveSpells
|
||||
? classSpellMap[charClassId].activeSpells
|
||||
: classSpellMap[charClassId].knownSpells
|
||||
);
|
||||
setAvailableSpells(
|
||||
shouldFetch ? classSpellMap[charClassId].availableSpells : []
|
||||
);
|
||||
setSourceCategories(classSpellMap[charClassId].sourceCategories);
|
||||
setLoadingStatus(DataLoadingStatusEnum.LOADED);
|
||||
},
|
||||
[spellsManager, hasActiveSpells, shouldFetch, charClassId]
|
||||
);
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [getData]);
|
||||
|
||||
function getCombinedSpells(): Array<SpellManagerType> {
|
||||
// const remainingLazySpells = availableSpells.filter((spell) => !knownSpellIds.includes(spell.deriveKnownKey()));
|
||||
|
||||
return sortBy(
|
||||
[...availableSpells, ...spells],
|
||||
[
|
||||
(spell) => spell.getLevel(),
|
||||
(spell) => spell.getName(),
|
||||
(spell) => spell.getExpandedDataOriginRef() !== null,
|
||||
(spell) => spell.getUniqueKey(),
|
||||
]
|
||||
);
|
||||
}
|
||||
function getFilteredSpells(
|
||||
combinedSpells: Array<SpellManagerType>
|
||||
): Array<SpellManagerType> {
|
||||
return combinedSpells.filter((spell) => {
|
||||
if (filterSourceCategories.length !== 0) {
|
||||
const sourceCategoryId = spell.getPrimarySourceCategoryId();
|
||||
if (!filterSourceCategories.includes(sourceCategoryId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filterLevels.length !== 0 &&
|
||||
!filterLevels.includes(spell.getLevel())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
filterQuery !== "" &&
|
||||
!FilterUtils.doesQueryMatchData(filterQuery, spell.getName())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function handleFilterSpellLevel(level: number): void {
|
||||
setFilterLevels(
|
||||
filterLevels.includes(level)
|
||||
? filterLevels.filter((l) => l !== level)
|
||||
: [...filterLevels, level]
|
||||
);
|
||||
}
|
||||
|
||||
function handleSourceCategoryClick(categoryId: number): void {
|
||||
setFilterSourceCategories(
|
||||
filterSourceCategories.includes(categoryId)
|
||||
? filterSourceCategories.filter((cat) => cat !== categoryId)
|
||||
: [...filterSourceCategories, categoryId]
|
||||
);
|
||||
}
|
||||
|
||||
function onSuccess() {
|
||||
getData();
|
||||
}
|
||||
function onFailure() {
|
||||
getData();
|
||||
}
|
||||
|
||||
function renderUi(): React.ReactNode {
|
||||
const combinedSpells = getCombinedSpells();
|
||||
const filteredSpells = getFilteredSpells(combinedSpells);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{showFilters && (
|
||||
<React.Fragment>
|
||||
<SpellFilter
|
||||
spells={combinedSpells.map((manager) => manager.getSpell())}
|
||||
sourceCategories={sourceCategories}
|
||||
filterQuery={filterQuery}
|
||||
filterLevels={filterLevels}
|
||||
onLevelFilterClick={handleFilterSpellLevel}
|
||||
onQueryChange={setFilterQuery}
|
||||
onSourceCategoryClick={handleSourceCategoryClick}
|
||||
filterSourceCategories={filterSourceCategories}
|
||||
themed
|
||||
/>
|
||||
<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>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{filteredSpells.length === 0 && <div>No Results Found</div>}
|
||||
{filteredSpells.map((spell, idx) => (
|
||||
<SpellManagerItem
|
||||
theme={theme}
|
||||
spell={spell}
|
||||
key={`${spell.getId()}-${idx}`}
|
||||
onPrepare={() => {
|
||||
spell.handlePrepare({ characterClassId }, onSuccess, onFailure);
|
||||
}}
|
||||
onUnprepare={() => {
|
||||
spell.handleUnprepare({ characterClassId }, onSuccess, onFailure);
|
||||
}}
|
||||
onRemove={() => {
|
||||
spell.handleRemove({ characterClassId }, onSuccess, onFailure);
|
||||
}}
|
||||
onAdd={() => {
|
||||
spell.handleAdd({ characterClassId }, onSuccess, onFailure);
|
||||
}}
|
||||
isPrepareMaxed={isPrepareMaxed}
|
||||
isCantripsKnownMaxed={isCantripsKnownMaxed}
|
||||
isSpellsKnownMaxed={isSpellsKnownMaxed}
|
||||
addButtonText={addButtonText}
|
||||
enableAdd={enableAdd}
|
||||
enablePrepare={enablePrepare}
|
||||
enableUnprepare={enableUnprepare}
|
||||
enableSpellRemove={enableSpellRemove}
|
||||
buttonUnprepareText={buttonUnprepareText}
|
||||
buttonActiveStyle={buttonActiveStyle}
|
||||
spellCasterInfo={spellCasterInfo}
|
||||
ruleData={ruleData}
|
||||
entityValueLookup={entityValueLookup}
|
||||
showExpandedType={showExpandedType ?? true}
|
||||
showCustomize={showCustomize}
|
||||
dataOriginRefData={dataOriginRefData}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLoading(): React.ReactNode {
|
||||
return <LoadingPlaceholder />;
|
||||
}
|
||||
|
||||
let content: React.ReactNode;
|
||||
switch (loadingStatus) {
|
||||
case DataLoadingStatusEnum.LOADED:
|
||||
content = renderUi();
|
||||
break;
|
||||
|
||||
case DataLoadingStatusEnum.LOADING:
|
||||
default:
|
||||
content = renderLoading();
|
||||
break;
|
||||
}
|
||||
|
||||
return <div className="ct-spell-manager">{content}</div>;
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleHeaderContent,
|
||||
} from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
DataOriginRefData,
|
||||
EntityValueLookup,
|
||||
RuleData,
|
||||
SpellCasterInfo,
|
||||
SpellManager,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { SpellName } from "~/components/SpellName";
|
||||
|
||||
import SpellDetail from "../../SpellDetail";
|
||||
import { RemoveButton, ThemeButton } from "../../common/Button";
|
||||
|
||||
interface Props {
|
||||
spell: SpellManager;
|
||||
buttonUnprepareText?: string;
|
||||
buttonActiveStyle?: string;
|
||||
enableSpellRemove: boolean;
|
||||
isPrepareMaxed: boolean;
|
||||
isCantripsKnownMaxed: boolean;
|
||||
isSpellsKnownMaxed: boolean;
|
||||
addButtonText: string;
|
||||
enableAdd: boolean;
|
||||
enablePrepare: boolean;
|
||||
enableUnprepare: boolean;
|
||||
showExpandedType: boolean;
|
||||
showCustomize: boolean;
|
||||
onPrepare: () => void;
|
||||
onUnprepare: () => void;
|
||||
onRemove: () => void;
|
||||
onAdd: () => void;
|
||||
spellCasterInfo: SpellCasterInfo;
|
||||
ruleData: RuleData;
|
||||
entityValueLookup: EntityValueLookup;
|
||||
dataOriginRefData: DataOriginRefData;
|
||||
proficiencyBonus: number;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
|
||||
export default function SpellManagerItem({
|
||||
spell,
|
||||
isPrepareMaxed,
|
||||
isCantripsKnownMaxed,
|
||||
isSpellsKnownMaxed,
|
||||
showExpandedType,
|
||||
showCustomize,
|
||||
onPrepare,
|
||||
onUnprepare,
|
||||
onRemove,
|
||||
onAdd,
|
||||
spellCasterInfo,
|
||||
ruleData,
|
||||
entityValueLookup,
|
||||
dataOriginRefData,
|
||||
proficiencyBonus,
|
||||
theme,
|
||||
enableSpellRemove = true,
|
||||
enableAdd = true,
|
||||
enablePrepare = true,
|
||||
enableUnprepare = true,
|
||||
buttonUnprepareText = "Prepared",
|
||||
addButtonText = "Prepare",
|
||||
buttonActiveStyle = "",
|
||||
}: Props) {
|
||||
function handlePrepareToggle(): void {
|
||||
if (spell.isPrepared()) {
|
||||
onUnprepare();
|
||||
} else {
|
||||
onPrepare();
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemove(): void {
|
||||
onRemove();
|
||||
}
|
||||
|
||||
function handleAdd(): void {
|
||||
onAdd();
|
||||
}
|
||||
|
||||
function renderButtons(classNames: Array<string>): React.ReactNode {
|
||||
const alwaysPrepared = spell.isAlwaysPrepared();
|
||||
const isPrepared = spell.isPrepared();
|
||||
const canRemove = spell.canRemove();
|
||||
const canPrepare = spell.canPrepare();
|
||||
const canAdd = spell.canAdd();
|
||||
const isCantrip = spell.isCantrip();
|
||||
|
||||
let isAddDisabled: boolean = false;
|
||||
if (isCantrip && isCantripsKnownMaxed) {
|
||||
isAddDisabled = true;
|
||||
}
|
||||
if (!isCantrip && isSpellsKnownMaxed) {
|
||||
isAddDisabled = true;
|
||||
}
|
||||
|
||||
let showPrepare: boolean =
|
||||
!alwaysPrepared && canPrepare && (enablePrepare || enableUnprepare);
|
||||
|
||||
let showRemove: boolean =
|
||||
canRemove &&
|
||||
(enableSpellRemove || (spell.isSpellbookCaster() && isCantrip));
|
||||
|
||||
if (isCantrip) {
|
||||
addButtonText = Constants.SpellCastingLearningStyleAddText[Constants.SpellCastingLearningStyle.Learned];
|
||||
buttonUnprepareText = Constants.SpellCastingLearningStyleRemoveText[Constants.SpellCastingLearningStyle.Learned];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")}>
|
||||
{alwaysPrepared && (
|
||||
<div className="ct-spell-manager__spell-always">Always Prepared</div>
|
||||
)}
|
||||
{showPrepare && (
|
||||
<ThemeButton
|
||||
size="small"
|
||||
disabled={isPrepareMaxed && !isPrepared}
|
||||
style={isPrepared ? buttonActiveStyle : "outline"}
|
||||
onClick={handlePrepareToggle}
|
||||
stopPropagation={true}
|
||||
>
|
||||
{isPrepared ? buttonUnprepareText : addButtonText}
|
||||
</ThemeButton>
|
||||
)}
|
||||
{showRemove && (
|
||||
<RemoveButton onClick={handleRemove} style={buttonActiveStyle}>
|
||||
{buttonUnprepareText}
|
||||
</RemoveButton>
|
||||
)}
|
||||
{enableAdd && canAdd && (
|
||||
<ThemeButton
|
||||
size="small"
|
||||
onClick={handleAdd}
|
||||
disabled={isAddDisabled}
|
||||
style={"outline"}
|
||||
stopPropagation={true}
|
||||
>
|
||||
{addButtonText}
|
||||
</ThemeButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderHeader(): React.ReactNode {
|
||||
return (
|
||||
<CollapsibleHeaderContent
|
||||
heading={
|
||||
<SpellName
|
||||
spell={spell.spell}
|
||||
showExpandedType={showExpandedType}
|
||||
dataOriginRefData={dataOriginRefData}
|
||||
showLegacy={true}
|
||||
/>
|
||||
}
|
||||
callout={renderButtons(["ct-spell-manager__spell-header-actions"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
layoutType={"minimal"}
|
||||
header={renderHeader()}
|
||||
className="ct-spell-manager__spell"
|
||||
>
|
||||
<SpellDetail
|
||||
theme={theme}
|
||||
spell={spell.spell}
|
||||
isPreparedMaxed={isPrepareMaxed}
|
||||
enableCaster={false}
|
||||
spellCasterInfo={spellCasterInfo}
|
||||
ruleData={ruleData}
|
||||
onPrepareToggle={handlePrepareToggle}
|
||||
onRemove={handleRemove}
|
||||
entityValueLookup={entityValueLookup}
|
||||
showCustomize={showCustomize}
|
||||
proficiencyBonus={proficiencyBonus}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import SpellManagerItem from "./SpellManagerItem";
|
||||
|
||||
export default SpellManagerItem;
|
||||
export { SpellManagerItem };
|
||||
@@ -0,0 +1,5 @@
|
||||
import SpellManager from "./SpellManager";
|
||||
import SpellManagerItem from "./SpellManagerItem";
|
||||
|
||||
export default SpellManager;
|
||||
export { SpellManager, SpellManagerItem };
|
||||
Reference in New Issue
Block a user