New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
@@ -0,0 +1,292 @@
import clsx from "clsx";
import { FC } from "react";
import { RuleDataUtils } from "@dndbeyond/character-rules-engine";
import GearIcon from "@dndbeyond/fontawesome-cache/svgs/regular/gear.svg";
import CloseIcon from "@dndbeyond/fontawesome-cache/svgs/regular/xmark.svg";
import { Button } from "~/components/Button";
import { CheckboxGroup, CheckboxInfo } from "~/components/CheckboxGroup";
import { Dialog, DialogProps } from "~/components/Dialog";
import { PrivacyTypeRadio } from "~/components/PrivacyTypeRadio";
import { SourceCategoryDescription } from "~/constants";
import { PreferenceAbilityScoreDisplayTypeEnum } from "~/constants";
import { useFeatureFlags } from "~/contexts/FeatureFlag";
import { generateCharacterPreferences } from "~/helpers/generateCharacterPreferences";
import PreferenceUpdateLocation from "~/tools/js/Shared/constants/PreferenceUpdateLocation";
import AbilitySummary from "~/tools/js/smartComponents/AbilitySummary";
import { useUserPreferences } from "../../contexts/UserPreferences";
import styles from "./styles.module.css";
type AbilityScoreDisplayTypes = {
label: string;
value: number;
}[];
export interface CharacterSettingsModalProps extends DialogProps {}
export const CharacterSettingsModal: FC<CharacterSettingsModalProps> = ({
className,
open,
onClose,
...props
}) => {
const {
privacyType,
isDarkModeEnabled,
abilityScoreDisplayType,
updatePreferences,
ruleData,
defaultEnabledSourceCategories,
isHomebrewEnabled,
} = useUserPreferences();
const { featureFlags } = useFeatureFlags();
const updateLocation = PreferenceUpdateLocation.CharacterListing;
// Build an array of display types from enum
const abilityScoreDisplayTypes: AbilityScoreDisplayTypes = Object.entries(
PreferenceAbilityScoreDisplayTypeEnum
)
// Remove any duplicates which don't have numeric values
.filter((opt) => opt.some((val) => typeof val === "number"))
// Map to object with label and value formatting
.map(([label, value]) => ({
label: label.replace("_", " "),
value: Number(value),
}));
const preferences = generateCharacterPreferences(featureFlags);
const getTheme = (darkMode: boolean) => ({
name: "DDB Red",
backgroundColor: darkMode ? "#10161ADB" : "#FEFEFE",
isDefault: true,
themeColor: "#C53131",
themeColorId: null,
isDarkMode: darkMode || false,
});
const rollContext = {
entityId: "52962664",
entityType: "character",
name: "Stor Hornraven",
avatarUrl:
"https://stg.dndbeyond.com/avatars/18589/680/1581111423-52962664.jpeg?width=150&height=150&fit=crop&quality=95&auto=webp",
};
const handleSourceCategoryChange = (
sourceId: number,
isActive: boolean
): void => {
const newSourceCategories = {
...defaultEnabledSourceCategories,
[sourceId]: isActive,
};
updatePreferences({
privacyType,
isDarkModeEnabled,
abilityScoreDisplayType,
updateLocation,
defaultEnabledSourceCategories: newSourceCategories,
isHomebrewEnabled,
});
};
let sourceToggles: Array<CheckboxInfo> = [];
let partneredSourceCheckboxes: Array<CheckboxInfo> = [];
let allPartneredSources: Array<number> = [];
if (ruleData) {
const activeSourceCategories = Object.keys(defaultEnabledSourceCategories)
.filter((key) => defaultEnabledSourceCategories[key])
.map(Number);
RuleDataUtils.getSourceCategories(ruleData).forEach((sourceCategory) => {
if (!sourceCategory.isToggleable) {
return null;
}
const checkbox: CheckboxInfo = {
label: `${sourceCategory.name}`,
initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
onChange: (e) =>
handleSourceCategoryChange(sourceCategory.id, e.target.checked),
sortOrder: sourceCategory.sortOrder,
description: sourceCategory.description ?? "",
};
if (sourceCategory.isPartneredContent) {
delete checkbox.description;
partneredSourceCheckboxes.push(checkbox);
allPartneredSources.push(sourceCategory.id);
} else {
sourceToggles.push(checkbox);
}
});
//Add Homebrew to sources for display
sourceToggles.push({
label: "Homebrew",
initiallyEnabled: isHomebrewEnabled,
description: SourceCategoryDescription.homebrew,
onChange: (e) => {
updatePreferences({
privacyType,
isDarkModeEnabled,
abilityScoreDisplayType,
updateLocation,
defaultEnabledSourceCategories,
isHomebrewEnabled: e.target.checked,
});
},
sortOrder: 0,
});
}
const handleChangeUnderdarkMode = () => {
const isChecked = !isDarkModeEnabled;
updatePreferences({
privacyType,
abilityScoreDisplayType,
updateLocation,
isDarkModeEnabled: isChecked,
defaultEnabledSourceCategories,
isHomebrewEnabled,
});
};
const handleChangeDisplayType = (value: number) => {
if (!isNaN(value))
updatePreferences({
privacyType,
isDarkModeEnabled,
updateLocation,
abilityScoreDisplayType: value,
defaultEnabledSourceCategories,
isHomebrewEnabled,
});
};
return (
<Dialog
className={clsx([styles.characterSettingsModal, className])}
open={open}
onClose={onClose}
modal
{...props}
>
<button
className={styles.closeButton}
onClick={onClose}
aria-label="Close the Character Settings Modal"
>
<CloseIcon />
</button>
<header className={styles.header}>
<GearIcon />
<h2>Default Character Settings</h2>
</header>
<section className={styles.body}>
<p className={styles.helpText}>
These settings apply defaults to all new Characters you create. Any
settings you apply to a specific Character will override the choices
you make here.
</p>
<CheckboxGroup
className={styles.checkboxGroup}
title="Sources"
description={SourceCategoryDescription.official}
checkboxes={sourceToggles}
name="sources"
showAccordion={true}
accordionDefaultOpen={true}
/>
<CheckboxGroup
className={styles.checkboxGroup}
title="Partnered Content"
description={SourceCategoryDescription.partnered}
checkboxes={partneredSourceCheckboxes}
name="partnered-content"
showAccordion={true}
accordionSummary="Choose Partners"
/>
<PrivacyTypeRadio
className={styles.privacyTypeRadio}
initialValue={privacyType}
onChange={(e) =>
updatePreferences({
isDarkModeEnabled,
abilityScoreDisplayType,
updateLocation,
privacyType: parseInt(e.target.value),
defaultEnabledSourceCategories,
isHomebrewEnabled,
})
}
/>
<div className={styles.displayGrid}>
<div className={styles.displayFields}>
<CheckboxGroup
className={styles.checkboxGroup}
title="Display"
name="underdark-mode"
data-testid="underdark-mode-toggle"
checkboxes={[
{
label: "Underdark Mode",
initiallyEnabled: isDarkModeEnabled,
onChange: handleChangeUnderdarkMode,
description: "Activate dark mode for Character Sheets.",
},
]}
/>
<div className={styles.abilityScores}>
<p className={styles.label}>Ability Scores / Modifiers</p>
<p className={styles.description}>
Reverse the display of Ability Scores and Modifiers.
</p>
<div className={styles.buttonGroup}>
{abilityScoreDisplayTypes
.reverse()
.map(({ label, value }, i) => (
<Button
className={clsx([
styles.buttonGroupButton,
abilityScoreDisplayType === value &&
styles.buttonGroupSelected,
])}
onClick={() => handleChangeDisplayType(Number(value))}
variant="outline"
size="small"
key={`ability-score-display-type-${value}`}
>
{label}
</Button>
))}
</div>
</div>
</div>
<div className={styles.displayPreview}>
<div className={styles.previewContainer}>
<p className={styles.previewText}>Preview</p>
<AbilitySummary
ability={{ name: "Strength", score: 14, modifier: 2 }}
preferences={{
...preferences,
abilityScoreDisplayType: abilityScoreDisplayType,
}}
theme={getTheme(isDarkModeEnabled || false)}
onClick={(e) => e.getBaseScore()}
rollContext={rollContext}
/>
</div>
</div>
</div>
</section>
</Dialog>
);
};