From 0b403376c5aae66f081d31bcbe1df5a2bdbbae44 Mon Sep 17 00:00:00 2001
From: David Kruger
Date: Wed, 18 Jun 2025 01:00:16 -0700
Subject: [PATCH] New source found from dndbeyond.com
---
ddb_main/components/SpellName/SpellName.tsx | 36 +++--
ddb_main/hooks/useCharacterEngine.ts | 3 -
.../es/engine/Background/accessors.js | 3 +
.../rules-engine/es/engine/Entity/utils.js | 2 +-
.../rules-engine/es/engine/Feat/accessors.js | 4 +
.../rules-engine/es/engine/Feat/generators.js | 19 ++-
.../es/engine/Prerequisite/accessors.js | 7 +
.../es/engine/Prerequisite/utils.js | 50 +++----
.../es/engine/Prerequisite/validators.js | 66 +++++----
.../es/engine/Spell/generators.js | 12 +-
.../rules-engine/es/managers/FeatManager.js | 45 ++++++
.../rules-engine/es/sagas/character/saga.js | 6 +-
.../rules-engine/es/sagas/serviceData/saga.js | 6 +-
.../es/selectors/composite/engine.js | 7 +-
.../HpManageModal/HpManageModal.tsx | 83 +++++------
.../components/InputField/InputField.tsx | 132 +++++++++++++++++
.../components/PortraitName/PortraitName.tsx | 20 ++-
.../panes/FeatsManagePane/Feat/Feat.tsx | 8 +-
.../AbilityScoreManagerManual.tsx | 35 +++--
.../DescriptionManage/DescriptionManage.tsx | 81 +++++-----
.../pages/HomeBasicInfo/HomeBasicInfo.tsx | 66 +++++----
.../components/ActionDetail/ActionDetail.tsx | 26 ++--
.../components/SpellManager/SpellManager.tsx | 2 +-
.../common/FormInputField/FormInputField.tsx | 139 ------------------
.../PrerequisiteFailureSummary.tsx | 44 ------
.../PrerequisiteFailureSummary/index.ts | 4 -
.../HpManageModal/styles.module.css?c97e | 2 +-
.../InputField/styles.module.css?ec81 | 2 +
.../styles.module.css?bb47 | 2 +
.../DescriptionManage/styles.module.css?cacc | 2 +-
30 files changed, 496 insertions(+), 418 deletions(-)
create mode 100644 ddb_main/subApps/builder/components/InputField/InputField.tsx
delete mode 100644 ddb_main/tools/js/Shared/components/common/FormInputField/FormInputField.tsx
delete mode 100644 ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/PrerequisiteFailureSummary.tsx
delete mode 100644 ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/index.ts
create mode 100644 ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/InputField/styles.module.css?ec81
create mode 100644 ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/styles.module.css?bb47
diff --git a/ddb_main/components/SpellName/SpellName.tsx b/ddb_main/components/SpellName/SpellName.tsx
index 93f7755..f565139 100644
--- a/ddb_main/components/SpellName/SpellName.tsx
+++ b/ddb_main/components/SpellName/SpellName.tsx
@@ -2,7 +2,6 @@ import clsx from "clsx";
import { FC, HTMLAttributes, useEffect, useState } from "react";
import { useSelector } from "react-redux";
-import { Tooltip } from "@dndbeyond/character-common-components/es";
import {
SpellUtils,
EntityUtils,
@@ -12,6 +11,7 @@ import {
characterEnvSelectors,
} from "@dndbeyond/character-rules-engine/es";
+import { Tooltip } from "~/components/Tooltip";
import { useCharacterTheme } from "~/contexts/CharacterTheme";
import { useUnpropagatedClick } from "~/hooks/useUnpropagatedClick";
@@ -77,23 +77,29 @@ export const SpellName: FC = ({
{...props}
>
{showExpandedType && expandedInfoText !== "" && (
-
- +
-
+ <>
+
+ +
+
+
+ >
)}
{SpellUtils.getName(spell)}
{SpellUtils.isCustomized(spell) && (
-
- *
-
+ <>
+
+ *
+
+
+ >
)}
{showIcons && SpellUtils.getConcentration(spell) && (
({
featOption: useSelector(s.getFeatOptionLookup),
featChoice: useSelector(s.getFeatChoiceLookup),
gearWeaponItems: useSelector(s.getGearWeaponItems),
- globalBackgroundSpellListIds: useSelector(s.getGlobalBackgroundSpellListIds),
- globalRaceSpellListIds: useSelector(s.getGlobalRaceSpellListIds),
- globalSpellListIds: useSelector(s.getGlobalSpellListIds),
hasInitiativeAdvantage: useSelector(s.getHasInitiativeAdvantage),
hasMaxAttunedItems: useSelector(s.hasMaxAttunedItems),
hasSpells: useSelector(s.hasSpells),
diff --git a/ddb_main/packages/rules-engine/es/engine/Background/accessors.js b/ddb_main/packages/rules-engine/es/engine/Background/accessors.js
index 06b6b06..097f986 100644
--- a/ddb_main/packages/rules-engine/es/engine/Background/accessors.js
+++ b/ddb_main/packages/rules-engine/es/engine/Background/accessors.js
@@ -450,3 +450,6 @@ export function getFeatListContracts(contract) {
var _a, _b;
return (_b = (_a = contract === null || contract === void 0 ? void 0 : contract.definition) === null || _a === void 0 ? void 0 : _a.grantedFeats) !== null && _b !== void 0 ? _b : [];
}
+export function getFeatLists(background) {
+ return background.featLists;
+}
diff --git a/ddb_main/packages/rules-engine/es/engine/Entity/utils.js b/ddb_main/packages/rules-engine/es/engine/Entity/utils.js
index 735b31e..f20dbf1 100644
--- a/ddb_main/packages/rules-engine/es/engine/Entity/utils.js
+++ b/ddb_main/packages/rules-engine/es/engine/Entity/utils.js
@@ -218,7 +218,7 @@ export function getDataOriginRefName(ref, refData, defaultValue, tryParent = fal
break;
}
case DataOriginTypeEnum.UNKNOWN: {
- extraDisplay = defaultValue !== null && defaultValue !== void 0 ? defaultValue : 'No Origin';
+ extraDisplay = defaultValue !== null && defaultValue !== void 0 ? defaultValue : 'Unknown Origin';
break;
}
default:
diff --git a/ddb_main/packages/rules-engine/es/engine/Feat/accessors.js b/ddb_main/packages/rules-engine/es/engine/Feat/accessors.js
index cdb803c..6b3f278 100644
--- a/ddb_main/packages/rules-engine/es/engine/Feat/accessors.js
+++ b/ddb_main/packages/rules-engine/es/engine/Feat/accessors.js
@@ -281,3 +281,7 @@ export function getDefinitionRepeatableParentId(feat) {
export function getRepeatableParentId(feat) {
return getDefinitionRepeatableParentId(feat);
}
+export function getSpellListIds(feat) {
+ var _a, _b;
+ return (_b = (_a = getDefinition(feat)) === null || _a === void 0 ? void 0 : _a.spellListIds) !== null && _b !== void 0 ? _b : [];
+}
diff --git a/ddb_main/packages/rules-engine/es/engine/Feat/generators.js b/ddb_main/packages/rules-engine/es/engine/Feat/generators.js
index 814486c..80ab8e5 100644
--- a/ddb_main/packages/rules-engine/es/engine/Feat/generators.js
+++ b/ddb_main/packages/rules-engine/es/engine/Feat/generators.js
@@ -14,7 +14,7 @@ import { ModifierGenerators } from '../Modifier';
import { OptionAccessors, OptionGenerators } from '../Option';
import { RaceAccessors } from '../Race';
import { SpellGenerators } from '../Spell';
-import { getComponentId, getComponentTypeId, getCreatureRules, getEntityTypeId, getId, getModifiers, getOptions, getUniqueKey, } from './accessors';
+import { getComponentId, getComponentTypeId, getCreatureRules, getEntityTypeId, getId, getModifiers, getOptions, getSpellListIds, getUniqueKey, } from './accessors';
/**
*
* @param feat
@@ -342,3 +342,20 @@ export function generateDataOriginPairedFeats(feats) {
}
return lookup;
}
+/**
+ * Generates a list of spell list IDs from the feats.
+ * This is used to determine which spell lists are available globally.
+ *
+ * @param feats - Array of Feat objects
+ * @returns Array of spell list IDs
+ */
+export function generateGlobalFeatsSpellListIds(feats) {
+ const spellListIds = [];
+ feats.forEach((feat) => {
+ const spellListIdsForFeat = getSpellListIds(feat);
+ if (spellListIdsForFeat.length > 0) {
+ spellListIds.push(...spellListIdsForFeat);
+ }
+ });
+ return spellListIds;
+}
diff --git a/ddb_main/packages/rules-engine/es/engine/Prerequisite/accessors.js b/ddb_main/packages/rules-engine/es/engine/Prerequisite/accessors.js
index 4ba7c96..ba643ae 100644
--- a/ddb_main/packages/rules-engine/es/engine/Prerequisite/accessors.js
+++ b/ddb_main/packages/rules-engine/es/engine/Prerequisite/accessors.js
@@ -79,6 +79,13 @@ export function getDescription(prerequisiteGrouping) {
export function getSubType(prerequisite) {
return prerequisite.subType;
}
+/**
+ *
+ * @param prerequisite
+ */
+export function getShouldExclude(prerequisite) {
+ return prerequisite.shouldExclude;
+}
/**
* @param prerequisite
*/
diff --git a/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js b/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js
index c769fef..26d80e1 100644
--- a/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js
+++ b/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js
@@ -1,6 +1,6 @@
import { AbilityAccessors } from '../Ability';
import { FormatUtils } from '../Format';
-import { getEntityId, getFriendlySubtypeName, getPrerequisites, getSubType, getType, getValue } from './accessors';
+import { getEntityId, getFriendlySubtypeName, getPrerequisites, getSubType, getType, getValue, getShouldExclude, } from './accessors';
import { PrerequisiteSubTypeEnum, PrerequisiteTypeEnum } from './constants';
import { validatePrerequisite } from './validators';
/**
@@ -13,19 +13,19 @@ export function getPrerequisiteFailure(prerequisite, prerequisiteData) {
case PrerequisiteTypeEnum.ABILITY_SCORE:
return getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteData);
case PrerequisiteTypeEnum.PROFICIENCY:
- return getPrerequisiteFailureProficiency(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureProficiency(prerequisite);
case PrerequisiteTypeEnum.SPECIES:
- return getPrerequisiteFailureRace(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureRace(prerequisite);
case PrerequisiteTypeEnum.SIZE:
- return getPrerequisiteFailureSize(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureSize(prerequisite);
case PrerequisiteTypeEnum.SPECIES_OPTION:
- return getPrerequisiteFailureSubrace(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureSubrace(prerequisite);
case PrerequisiteTypeEnum.LEVEL:
- return getPrerequisiteFailureLevel(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureLevel(prerequisite);
case PrerequisiteTypeEnum.CLASS:
- return getPrerequisiteFailureClass(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureClass(prerequisite);
case PrerequisiteTypeEnum.FEAT:
- return getPrerequisiteFailureFeat(prerequisite, prerequisiteData);
+ return getPrerequisiteFailureFeat(prerequisite);
case PrerequisiteTypeEnum.CLASS_FEATURE:
return getPrerequisiteFailureClassFeature(prerequisite);
case PrerequisiteTypeEnum.CUSTOM_VALUE:
@@ -39,7 +39,7 @@ export function getPrerequisiteFailure(prerequisite, prerequisiteData) {
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureLevel(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureLevel(prerequisite) {
let requiredDescription = '';
switch (getSubType(prerequisite)) {
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
@@ -56,6 +56,7 @@ export function getPrerequisiteFailureLevel(prerequisite, prerequisiteData) {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -81,6 +82,7 @@ export function getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteDat
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: `${getFriendlySubtypeName(prerequisite)} ${getValue(prerequisite)}+`,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -88,13 +90,14 @@ export function getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteDat
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureProficiency(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureProficiency(prerequisite) {
return {
type: PrerequisiteTypeEnum.PROFICIENCY,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Proficiency`,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -102,13 +105,14 @@ export function getPrerequisiteFailureProficiency(prerequisite, prerequisiteData
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureRace(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureRace(prerequisite) {
return {
type: PrerequisiteTypeEnum.SPECIES,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: getFriendlySubtypeName(prerequisite),
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -116,13 +120,14 @@ export function getPrerequisiteFailureRace(prerequisite, prerequisiteData) {
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureSubrace(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureSubrace(prerequisite) {
return {
type: PrerequisiteTypeEnum.SPECIES_OPTION,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: getFriendlySubtypeName(prerequisite),
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -130,13 +135,14 @@ export function getPrerequisiteFailureSubrace(prerequisite, prerequisiteData) {
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureSize(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureSize(prerequisite) {
return {
type: PrerequisiteTypeEnum.SIZE,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Size`,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -144,13 +150,14 @@ export function getPrerequisiteFailureSize(prerequisite, prerequisiteData) {
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureClass(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureClass(prerequisite) {
return {
type: PrerequisiteTypeEnum.CLASS,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
export const getPrerequisiteFailureClassFeature = (prerequisite) => ({
@@ -159,19 +166,21 @@ export const getPrerequisiteFailureClassFeature = (prerequisite) => ({
requiredChoice: prerequisite.friendlySubTypeName,
requiredDescription: prerequisite.friendlySubTypeName,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
});
/**
*
* @param prerequisite
* @param prerequisiteData
*/
-export function getPrerequisiteFailureFeat(prerequisite, prerequisiteData) {
+export function getPrerequisiteFailureFeat(prerequisite) {
return {
type: PrerequisiteTypeEnum.FEAT,
data: {
requiredChoice: getFriendlySubtypeName(prerequisite),
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
},
+ shouldExclude: !!getShouldExclude(prerequisite),
};
}
/**
@@ -198,14 +207,3 @@ export function getPrerequisiteGroupingFailures(prerequisiteGrouping, prerequisi
}
return groupingFailures.filter((group) => group.length);
}
-/**
- * Get all prerequisites of a specific type
- * @param type
- * @param prereqGroups
- */
-export function getPrereqsByType(type, prereqGroups) {
- return prereqGroups.reduce((acc, prereq) => {
- const featMappings = getPrerequisites(prereq).filter((mapping) => getType(mapping) === type);
- return acc.concat(featMappings);
- }, []);
-}
diff --git a/ddb_main/packages/rules-engine/es/engine/Prerequisite/validators.js b/ddb_main/packages/rules-engine/es/engine/Prerequisite/validators.js
index 6385815..77dc559 100644
--- a/ddb_main/packages/rules-engine/es/engine/Prerequisite/validators.js
+++ b/ddb_main/packages/rules-engine/es/engine/Prerequisite/validators.js
@@ -2,9 +2,8 @@ import { AbilityAccessors } from '../Ability';
import { ClassAccessors } from '../Class';
import { HelperUtils } from '../Helper';
import { RaceAccessors } from '../Race';
-import { getEntityId, getEntityKey, getEntityTypeId, getPrerequisites, getSubType, getType, getValue, } from './accessors';
+import { getEntityId, getEntityKey, getEntityTypeId, getPrerequisites, getShouldExclude, getSubType, getType, getValue, } from './accessors';
import { PrerequisiteSubTypeEnum, PrerequisiteTypeEnum } from './constants';
-import { getPrereqsByType } from './utils';
/**
*
* @param prerequisiteGrouping
@@ -63,9 +62,12 @@ export function validatePrerequisite(prerequisite, prerequisiteData) {
return true;
}
export function validatePrerequisiteClassFeature(prerequisite, prerequisiteData) {
+ const shouldExclude = getShouldExclude(prerequisite);
if (!prerequisite.entityId)
return true;
- return Object.prototype.hasOwnProperty.call(prerequisiteData.classFeatureLookup, prerequisite.entityId);
+ // check if class feature exists in the class feature lookup
+ const hasClassFeature = Object.prototype.hasOwnProperty.call(prerequisiteData.classFeatureLookup, prerequisite.entityId);
+ return shouldExclude ? !hasClassFeature : hasClassFeature;
}
/**
*
@@ -73,10 +75,13 @@ export function validatePrerequisiteClassFeature(prerequisite, prerequisiteData)
* @param prerequisiteData
*/
export function validatePrerequisiteLevel(prerequisite, prerequisiteData) {
+ const shouldExclude = getShouldExclude(prerequisite);
switch (getSubType(prerequisite)) {
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
const value = getValue(prerequisite);
- return prerequisiteData.characterLevel >= (value ? value : 0);
+ // check if the character level is greater than or equal to the value
+ const characterLevelBeatsValue = prerequisiteData.characterLevel >= (value || 0);
+ return shouldExclude ? !characterLevelBeatsValue : characterLevelBeatsValue;
default:
// not implemented
}
@@ -101,6 +106,12 @@ export function validatePrerequisiteAbilityScore(prerequisite, prerequisiteData)
if (value === null) {
return true;
}
+ const shouldExclude = getShouldExclude(prerequisite);
+ // if shouldExclude is true, we want to check if the ability score is less than the value
+ if (shouldExclude) {
+ return abilityScore < value;
+ }
+ // if shouldExclude is false, we want to check if the ability score is greater than or equal to the value
return abilityScore >= value;
}
/**
@@ -109,7 +120,10 @@ export function validatePrerequisiteAbilityScore(prerequisite, prerequisiteData)
* @param prerequisiteData
*/
export function validatePrerequisiteProficiency(prerequisite, prerequisiteData) {
- return prerequisiteData.proficiencyLookup.hasOwnProperty(getEntityKey(prerequisite));
+ const shouldExclude = getShouldExclude(prerequisite);
+ //check if the proficiency exists in the proficiency lookup
+ const hasProficiency = prerequisiteData.proficiencyLookup.hasOwnProperty(getEntityKey(prerequisite));
+ return shouldExclude ? !hasProficiency : hasProficiency;
}
/**
*
@@ -120,10 +134,11 @@ export function validatePrerequisiteRace(prerequisite, prerequisiteData) {
if (!prerequisiteData.race) {
return true;
}
+ const shouldExclude = getShouldExclude(prerequisite);
// check if race is a base race
if (RaceAccessors.getBaseRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
RaceAccessors.getBaseRaceTypeId(prerequisiteData.race) === getEntityTypeId(prerequisite)) {
- return true;
+ return !shouldExclude;
}
// check if race is specific race
return validatePrerequisiteEntityRace(prerequisite, prerequisiteData);
@@ -149,8 +164,11 @@ function validatePrerequisiteEntityRace(prerequisite, prerequisiteData) {
if (!prerequisiteData.race) {
return true;
}
- return (RaceAccessors.getEntityRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
- RaceAccessors.getEntityRaceTypeId(prerequisiteData.race) === getEntityTypeId(prerequisite));
+ const shouldExclude = getShouldExclude(prerequisite);
+ // check if the race type and id match the prerequisite
+ const isMatch = RaceAccessors.getEntityRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
+ RaceAccessors.getEntityRaceTypeId(prerequisiteData.race) === getEntityTypeId(prerequisite);
+ return shouldExclude ? !isMatch : isMatch;
}
/**
*
@@ -165,7 +183,10 @@ export function validatePrerequisiteSize(prerequisite, prerequisiteData) {
if (!sizeInfo) {
return true;
}
- return sizeInfo.id === getEntityId(prerequisite) && sizeInfo.entityTypeId === getEntityTypeId(prerequisite);
+ const shouldExclude = getShouldExclude(prerequisite);
+ // check if size matches the prerequisite size
+ const isMatch = sizeInfo.id === getEntityId(prerequisite) && sizeInfo.entityTypeId === getEntityTypeId(prerequisite);
+ return shouldExclude ? !isMatch : isMatch;
}
/**
*
@@ -177,24 +198,10 @@ export function validatePrerequisiteClass(prerequisite, prerequisiteData) {
if (entityId === null) {
return true;
}
- return Object.values(prerequisiteData.baseClassLookup).some((charClass) => ClassAccessors.getId(charClass) === entityId);
-}
-/**
- * @param prerequisiteGrouping
- * @param prerequisiteData
- */
-export function validatePrerequisiteGroupingFeat(prerequisiteGrouping, prerequisiteData) {
- if (prerequisiteGrouping === null) {
- return true;
- }
- const mappings = getPrereqsByType(PrerequisiteTypeEnum.FEAT, prerequisiteGrouping);
- let isValid = true;
- mappings.forEach((prerequisite) => {
- if (!validatePrerequisiteFeat(prerequisite, prerequisiteData)) {
- isValid = false;
- }
- });
- return isValid;
+ const shouldExclude = getShouldExclude(prerequisite);
+ // check if class exists in the base class lookup
+ const hasClass = Object.values(prerequisiteData.baseClassLookup).some((charClass) => ClassAccessors.getId(charClass) === entityId);
+ return shouldExclude ? !hasClass : hasClass;
}
/**
*
@@ -206,5 +213,8 @@ export function validatePrerequisiteFeat(prerequisite, prerequisiteData) {
if (entityId === null) {
return true;
}
- return !!HelperUtils.lookupDataOrFallback(prerequisiteData.featLookup, entityId);
+ const shouldExclude = getShouldExclude(prerequisite);
+ // check if feat exists in the feat lookup
+ const hasFeat = !!HelperUtils.lookupDataOrFallback(prerequisiteData.featLookup, entityId);
+ return shouldExclude ? !hasFeat : hasFeat;
}
diff --git a/ddb_main/packages/rules-engine/es/engine/Spell/generators.js b/ddb_main/packages/rules-engine/es/engine/Spell/generators.js
index 9a6d8a3..fc40257 100644
--- a/ddb_main/packages/rules-engine/es/engine/Spell/generators.js
+++ b/ddb_main/packages/rules-engine/es/engine/Spell/generators.js
@@ -638,7 +638,7 @@ export function generateActiveCharacterSpells(spells, inventoryInfusionLookup, c
* @param optionalClassFeatures
* @param definitionPool
*/
-export function generateSpellListDataOriginLookup(race, classes, background, optionalOrigins, optionalClassFeatures, definitionPool) {
+export function generateSpellListDataOriginLookup(race, classes, background, optionalOrigins, optionalClassFeatures, definitionPool, feats) {
let lookup = {};
if (race !== null) {
RaceAccessors.getDefinitionRacialTraits(race).forEach((feature) => {
@@ -662,6 +662,12 @@ export function generateSpellListDataOriginLookup(race, classes, background, opt
});
});
});
+ feats.forEach((feat) => {
+ const uniqueKey = FeatAccessors.getUniqueKey(feat);
+ FeatAccessors.getSpellListIds(feat).forEach((spellListId) => {
+ lookup[spellListId] = DataOriginGenerators.generateDataOriginRef(DataOriginTypeEnum.FEAT, uniqueKey);
+ });
+ });
optionalOrigins.forEach((optionalOrigin) => {
const definitionKey = OptionalOriginAccessors.getDefinitionKey(optionalOrigin);
if (!definitionKey) {
@@ -697,6 +703,6 @@ export function generateSpellListDataOriginLookup(race, classes, background, opt
* @param raceSpellListIds
* @param backgroundSpellListIds
*/
-export function generateGlobalSpellListIds(raceSpellListIds, backgroundSpellListIds) {
- return [...raceSpellListIds, ...backgroundSpellListIds];
+export function generateGlobalSpellListIds(raceSpellListIds, backgroundSpellListIds, featSpellListIds) {
+ return [...raceSpellListIds, ...backgroundSpellListIds, ...featSpellListIds];
}
diff --git a/ddb_main/packages/rules-engine/es/managers/FeatManager.js b/ddb_main/packages/rules-engine/es/managers/FeatManager.js
index ee10d20..185fe25 100644
--- a/ddb_main/packages/rules-engine/es/managers/FeatManager.js
+++ b/ddb_main/packages/rules-engine/es/managers/FeatManager.js
@@ -1,3 +1,4 @@
+import sortBy from 'lodash/sortBy';
import { characterActions } from "../actions";
import { ChoiceUtils } from "../engine/Choice";
import { DisplayConfigurationTypeEnum } from "../engine/Core";
@@ -70,10 +71,54 @@ export class FeatManager extends BaseManager {
// user-facing categories from technical-only tags.
this.getCategories = () => FeatAccessors.getCategories(this.feat);
//Utils
+ //Gets all the prereq Failures for this feat - returns an array of arrays, where each inner array is a grouping of failures
this.getPrerequisiteFailures = () => {
const prerequisiteData = rulesEngineSelectors.getPrerequisiteData(this.state);
return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData);
};
+ // Creates a string that summarizes the prerequisite failures for this feat
+ this.getPrerequisiteFailuresText = () => {
+ //get all the prerequisite failures, sorted so that if every group failure is a missing prereq, they come first
+ const failures = sortBy(this.getPrerequisiteFailures(), (failureGroup) => failureGroup.every((f) => !f.shouldExclude) ? 0 : 1);
+ // Create an intro string for the summary
+ let intro = '';
+ // Check if all failures are either missing or prohibited
+ const hasOnlyMissingFailures = failures.every((failureGroup) => failureGroup.every((f) => !f.shouldExclude));
+ const hasOnlyProhibitedFailures = failures.every((failureGroup) => failureGroup.every((f) => f.shouldExclude));
+ // Determine the intro text based on the type of failures
+ if (hasOnlyMissingFailures) {
+ intro = 'Missing: ';
+ }
+ else if (hasOnlyProhibitedFailures) {
+ intro = 'Prohibits: ';
+ }
+ else {
+ intro = 'Requires: ';
+ }
+ // Each failure is sorted so that prohibited failures come last, and then combined into a single string
+ const combinedStrings = failures
+ .map((failure) => sortBy(failure, (f) => (f.shouldExclude ? 1 : 0))
+ .map((failureAnd) => {
+ if (failureAnd.shouldExclude && !hasOnlyProhibitedFailures && !hasOnlyMissingFailures) {
+ return `Prohibits ${failureAnd.data.requiredDescription}`;
+ }
+ return `${failureAnd.data.requiredDescription}`;
+ })
+ .reduce((acc, desc, idx) => {
+ let connector = '';
+ if (idx < failure.length - 2) {
+ connector = ', ';
+ }
+ else if (idx < failure.length - 1) {
+ connector = ' and ';
+ }
+ acc += `${desc}${connector}`;
+ return acc;
+ }, ''))
+ .join(' or ');
+ // return the intro text followed by the combined strings
+ return `${intro}${combinedStrings}`;
+ };
this.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat);
this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null;
this.getDefinitionKey = () => {
diff --git a/ddb_main/packages/rules-engine/es/sagas/character/saga.js b/ddb_main/packages/rules-engine/es/sagas/character/saga.js
index c446aa9..4507020 100644
--- a/ddb_main/packages/rules-engine/es/sagas/character/saga.js
+++ b/ddb_main/packages/rules-engine/es/sagas/character/saga.js
@@ -144,7 +144,7 @@ function* filter(action) {
*
* @param action
*/
-function* executePostAction(action) {
+export function* executePostAction(action) {
if (isPostAction(action.meta)) {
for (let i = 0; i < action.meta.postAction.type.length; i++) {
yield put({
@@ -184,7 +184,7 @@ function* executeSyncTransactionDeactivate(action, transactionInitiatorId) {
*
* @param action
*/
-function* executeApi(action) {
+export function* executeApi(action) {
let apiLookup = {
// ACTION
[types.ACTION_USE_SET]: apiShared.putCharacterActionLimitedUse,
@@ -392,7 +392,7 @@ function* executeApi(action) {
yield call(apiRequest, apiPayload);
}
}
-function* executeHandler(action) {
+export function* executeHandler(action) {
let handlerLookup = {
//ACTION
[types.ACTION_CUSTOMIZATIONS_DELETE]: sagaHandlers.handleActionCustomizationsDelete,
diff --git a/ddb_main/packages/rules-engine/es/sagas/serviceData/saga.js b/ddb_main/packages/rules-engine/es/sagas/serviceData/saga.js
index c216717..0d31380 100644
--- a/ddb_main/packages/rules-engine/es/sagas/serviceData/saga.js
+++ b/ddb_main/packages/rules-engine/es/sagas/serviceData/saga.js
@@ -97,7 +97,7 @@ function* filter(action) {
*
* @param action
*/
-function* executePostAction(action) {
+export function* executePostAction(action) {
if (isPostAction(action.meta)) {
for (let i = 0; i < action.meta.postAction.type.length; i++) {
yield put({
@@ -137,7 +137,7 @@ function* executeSyncTransactionDeactivate(action, transactionInitiatorId) {
*
* @param action
*/
-function* executeApi(action) {
+export function* executeApi(action) {
let apiLookup = {
//KNOWN_INFUSION
[types.KNOWN_INFUSION_MAPPING_SET]: apiShared.putCharacterKnownInfusion,
@@ -207,7 +207,7 @@ function* executeApi(action) {
yield call(apiRequest, apiPayload);
}
}
-function* executeHandler(action) {
+export function* executeHandler(action) {
let handlerLookup = {
//KNOWN_INFUSION
[types.KNOWN_INFUSION_MAPPING_CREATE]: sagaHandlers.handleKnownInfusionCreate,
diff --git a/ddb_main/packages/rules-engine/es/selectors/composite/engine.js b/ddb_main/packages/rules-engine/es/selectors/composite/engine.js
index 012a67b..69c06c0 100644
--- a/ddb_main/packages/rules-engine/es/selectors/composite/engine.js
+++ b/ddb_main/packages/rules-engine/es/selectors/composite/engine.js
@@ -68,6 +68,7 @@ export const getSpellListDataOriginLookup = createSelector([
characterSelectors.getOptionalOrigins,
characterSelectors.getOptionalClassFeatures,
serviceDataSelectors.getDefinitionPool,
+ characterSelectors.getFeats,
], SpellGenerators.generateSpellListDataOriginLookup);
/**
* @returns {Record>}
@@ -400,7 +401,11 @@ export const getGlobalBackgroundSpellListIds = createSelector([getBackgroundInfo
/**
* @returns {Array}
*/
-export const getGlobalSpellListIds = createSelector([getGlobalRaceSpellListIds, getGlobalBackgroundSpellListIds], SpellGenerators.generateGlobalSpellListIds);
+export const getGlobalFeatsSpellListIds = createSelector([getFeats], FeatGenerators.generateGlobalFeatsSpellListIds);
+/**
+ * @returns {Array}
+ */
+export const getGlobalSpellListIds = createSelector([getGlobalRaceSpellListIds, getGlobalBackgroundSpellListIds, getGlobalFeatsSpellListIds], SpellGenerators.generateGlobalSpellListIds);
/**
* @returns {DecorationInfo}
*/
diff --git a/ddb_main/subApps/builder/components/HpManageModal/HpManageModal.tsx b/ddb_main/subApps/builder/components/HpManageModal/HpManageModal.tsx
index e411a54..dbdbbc4 100644
--- a/ddb_main/subApps/builder/components/HpManageModal/HpManageModal.tsx
+++ b/ddb_main/subApps/builder/components/HpManageModal/HpManageModal.tsx
@@ -1,5 +1,5 @@
import clsx from "clsx";
-import { FC, HTMLAttributes, useMemo, useState } from "react";
+import { FC, FocusEvent, useMemo, useState } from "react";
import { useDispatch } from "react-redux";
import {
@@ -19,8 +19,8 @@ import {
HP_BONUS_VALUE,
HP_OVERRIDE_MAX_VALUE,
} from "~/subApps/sheet/constants";
-import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
+import { InputField } from "../InputField";
import styles from "./styles.module.css";
export interface HpManageModalProps
@@ -65,52 +65,55 @@ export const HpManageModal: FC = ({
reset();
};
- const transformBaseHp = (value: string): number => {
+ const handleBaseHp = (e: FocusEvent) => {
let parsedNumber = HelperUtils.parseInputInt(
- value,
+ e.target.value,
RuleDataUtils.getMinimumHpTotal(ruleData)
);
- return HelperUtils.clampInt(
+
+ const clampedValue = HelperUtils.clampInt(
parsedNumber,
RuleDataUtils.getMinimumHpTotal(ruleData),
HP_BASE_MAX_VALUE
);
+
+ setBaseHp(clampedValue);
};
- const transformBonusHp = (value: string): number | null => {
- let parsedNumber = HelperUtils.parseInputInt(value);
- if (parsedNumber === null) {
- return parsedNumber;
- }
- return HelperUtils.clampInt(
+ const handleBonusHp = (e: FocusEvent) => {
+ let parsedNumber = HelperUtils.parseInputInt(e.target.value);
+
+ if (parsedNumber === null) return parsedNumber;
+
+ const clampedValue = HelperUtils.clampInt(
parsedNumber,
HP_BONUS_VALUE.MIN,
HP_BONUS_VALUE.MAX
);
+
+ setBonusHp(clampedValue);
};
- const transformOverrideHp = (value: string): number | null => {
- let parsedNumber = HelperUtils.parseInputInt(value, null);
+ const handleOverrideHp = (e: FocusEvent) => {
+ let parsedNumber = HelperUtils.parseInputInt(e.target.value, null);
+
if (parsedNumber === null) {
+ setOverrideHp(null);
return parsedNumber;
}
- return HelperUtils.clampInt(
+
+ const clampedValue = HelperUtils.clampInt(
parsedNumber,
RuleDataUtils.getMinimumHpTotal(ruleData)
);
- };
- const handleOverrideHpUpdate = (value: number | null): void => {
- const newOverride =
- value === null
- ? null
- : HelperUtils.clampInt(
- value,
- RuleDataUtils.getMinimumHpTotal(ruleData),
- HP_OVERRIDE_MAX_VALUE
- );
+ const clampedOverride = HelperUtils.clampInt(
+ clampedValue,
+ RuleDataUtils.getMinimumHpTotal(ruleData),
+ HP_OVERRIDE_MAX_VALUE
+ );
- setOverrideHp(newOverride);
+ setOverrideHp(clampedOverride);
};
const totalHp = useMemo(() => {
@@ -148,35 +151,33 @@ export const HpManageModal: FC = ({
{baseHp}
) : (
- setBaseHp(value as number)}
- transformValueOnBlur={transformBaseHp}
+ onBlur={handleBaseHp}
/>
)}
- setBonusHp(value as number)}
- transformValueOnBlur={transformBonusHp}
+ placeholder="--"
+ onBlur={handleBonusHp}
/>
-
- }
+
diff --git a/ddb_main/subApps/builder/components/InputField/InputField.tsx b/ddb_main/subApps/builder/components/InputField/InputField.tsx
new file mode 100644
index 0000000..98d5bd9
--- /dev/null
+++ b/ddb_main/subApps/builder/components/InputField/InputField.tsx
@@ -0,0 +1,132 @@
+import clsx from "clsx";
+import {
+ ChangeEvent,
+ FC,
+ FocusEvent,
+ HTMLAttributes,
+ HTMLInputTypeAttribute,
+ useEffect,
+ useState,
+} from "react";
+import { v4 as uuidv4 } from "uuid";
+
+import styles from "./styles.module.css";
+
+interface InputProps extends HTMLAttributes {
+ min?: number;
+ max?: number;
+ autoComplete?: string;
+}
+
+export interface InputFieldProps extends HTMLAttributes {
+ errorMessage?: string;
+ initialValue?: string | number | null;
+ label: string;
+ maxLength?: number;
+ placeholder?: string;
+ type?: HTMLInputTypeAttribute;
+ inputProps?: InputProps;
+}
+
+export const InputField: FC = ({
+ className,
+ errorMessage,
+ initialValue = "",
+ inputProps,
+ label,
+ maxLength,
+ onBlur,
+ onChange,
+ onFocus,
+ placeholder,
+ type = "text",
+ ...props
+}) => {
+ // Set default error message if not provided
+ errorMessage ||= `The maximum length is ${maxLength} characters.`;
+ // Set up component state and a shared id
+ const [value, setValue] = useState(initialValue);
+ const [showError, setShowError] = useState(false);
+ const id = props.id ?? `input-field-${uuidv4()}`;
+
+ const handleChange = (e: ChangeEvent) => {
+ const valueLength = e.target.value.length;
+ if (maxLength) {
+ // If the value length exceeds maxLength, show error
+ if (valueLength > maxLength) {
+ setShowError(true);
+ //return; Prevent further processing if maxLength exceeded
+ return;
+ }
+
+ // If the value length is equal to maxLength, show error
+ if (valueLength === maxLength) {
+ setShowError(true);
+ }
+ // If the value length is less than maxLength, hide error
+ if (valueLength < maxLength) {
+ setShowError(false);
+ }
+ }
+ // Update component state
+ setValue(e.target.value);
+ // Call the provided onChange handler if it exists
+ if (onChange) onChange(e);
+ return;
+ };
+
+ const handleBlur = (e: FocusEvent) => {
+ const intValue = parseInt(e.target.value);
+
+ // If entered value is below the minimum, reset to minimum
+ if (!isNaN(intValue) && inputProps?.min && intValue < inputProps?.min) {
+ setValue(inputProps.min);
+ }
+
+ // If entered value is above the maximum and no initialValue exists, reset to maximum
+ if (!isNaN(intValue) && inputProps?.max && intValue > inputProps?.max) {
+ setValue(inputProps.max);
+ }
+
+ // Call the provided onBlur handler if it exists
+ if (onBlur) onBlur(e);
+
+ // Reset error state on blur
+ setShowError(false);
+ };
+
+ const handleFocus = (e: FocusEvent) => {
+ // Call the provided onFocus handler if it exists
+ if (onFocus) onFocus(e);
+ // Check for maxLength error
+ const valueLength = e.target.value.length;
+ if (maxLength && valueLength > maxLength - 1) setShowError(true);
+ };
+
+ useEffect(() => {
+ // Update the value when initialValue changes
+ setValue(initialValue);
+ // Reset error state when initial value changes
+ setShowError(false);
+ }, [initialValue]);
+
+ const inputAttrs = { id, type, placeholder };
+
+ return (
+
+
+ {label}
+
+
+ {showError && {errorMessage} }
+
+ );
+};
diff --git a/ddb_main/subApps/builder/components/PortraitName/PortraitName.tsx b/ddb_main/subApps/builder/components/PortraitName/PortraitName.tsx
index a1bcbca..8730a68 100644
--- a/ddb_main/subApps/builder/components/PortraitName/PortraitName.tsx
+++ b/ddb_main/subApps/builder/components/PortraitName/PortraitName.tsx
@@ -16,11 +16,11 @@ import {
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import { builderActions } from "~/tools/js/CharacterBuilder/actions";
import { builderSelectors } from "~/tools/js/CharacterBuilder/selectors";
-import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
import { PortraitManager } from "~/tools/js/Shared/containers/panes/DecoratePane";
import Tooltip from "~/tools/js/commonComponents/Tooltip";
import { CharacterAvatarPortrait } from "~/tools/js/smartComponents/CharacterAvatar";
+import { InputField } from "../InputField";
import styles from "./styles.module.css";
interface Props extends HTMLAttributes {
@@ -56,7 +56,7 @@ export const PortraitName: FC = ({
setCurrentSuggestedNames(suggestedNames);
}, [suggestedNames]);
- const handleNameFieldSet = (value: string | null): void => {
+ const handleNameFieldSet = (value: string) => {
let updatedName = value ?? "";
// If the flag useDefaultCharacterName is true and value is falsy, set the name to the DefaultCharacterName
@@ -137,18 +137,16 @@ export const PortraitName: FC = ({
>
)}
-
- }
maxLength={InputLimits.characterNameMaxLength}
- maxLengthErrorMsg={CharacterNameLimitMsg}
+ errorMessage={CharacterNameLimitMsg}
+ onBlur={(e) => handleNameFieldSet(e.target.value)}
+ inputProps={{
+ spellCheck: false,
+ autoComplete: "off",
+ }}
/>
= ({
{showFailures && (
-
+
+ {feat.getPrerequisiteFailuresText()}
+
)}
}
diff --git a/ddb_main/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/AbilityScoreManagerManual.tsx b/ddb_main/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/AbilityScoreManagerManual.tsx
index 04b2287..26c351f 100644
--- a/ddb_main/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/AbilityScoreManagerManual.tsx
+++ b/ddb_main/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/AbilityScoreManagerManual.tsx
@@ -1,9 +1,13 @@
+import { FocusEvent } from "react";
+
import {
AbilityManager,
HelperUtils,
} from "@dndbeyond/character-rules-engine/es";
-import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
+import { InputField } from "~/subApps/builder/components/InputField";
+
+import styles from "./styles.module.css";
interface Props {
abilities: Array;
@@ -16,14 +20,19 @@ export default function AbilityScoreManagerManual({
minScore = 3,
maxScore = 18,
}: Props) {
- const handleTransformValueOnBlur = (value: string): number | null => {
- const parsedValue = HelperUtils.parseInputInt(value);
+ const handleBlur = (
+ e: FocusEvent,
+ abilityScore: AbilityManager
+ ) => {
+ // Get the int value from the input
+ const parsedValue = HelperUtils.parseInputInt(e.target.value);
+ // Create a clamped variable to hold the clamped value
let clampedValue: number | null = null;
- if (parsedValue !== null) {
+ // If the value is valid, clamp it to the min and max scores
+ if (parsedValue !== null)
clampedValue = HelperUtils.clampInt(parsedValue, minScore, maxScore);
- }
-
- return clampedValue;
+ // Set the score change from the clampped value
+ abilityScore.handleScoreChange(clampedValue);
};
return (
@@ -35,11 +44,17 @@ export default function AbilityScoreManagerManual({
key={abilityScore.getId()}
data-stat-id={abilityScore.getId()}
>
- abilityScore.handleScoreChange(Number(value))}
+ handleBlur(e, abilityScore)}
/>
Total: {abilityScore.getTotalScore() ?? "--"}
diff --git a/ddb_main/tools/js/CharacterBuilder/containers/pages/DescriptionManage/DescriptionManage.tsx b/ddb_main/tools/js/CharacterBuilder/containers/pages/DescriptionManage/DescriptionManage.tsx
index c419ee9..4a49e6b 100644
--- a/ddb_main/tools/js/CharacterBuilder/containers/pages/DescriptionManage/DescriptionManage.tsx
+++ b/ddb_main/tools/js/CharacterBuilder/containers/pages/DescriptionManage/DescriptionManage.tsx
@@ -1,6 +1,6 @@
import axios, { Canceler } from "axios";
import { orderBy } from "lodash";
-import React, { HTMLAttributes, useContext } from "react";
+import React, { FocusEvent, HTMLAttributes, useContext } from "react";
import { DispatchProp } from "react-redux";
import { LoadingPlaceholder, Select } from "@dndbeyond/character-components/es";
@@ -52,12 +52,12 @@ import { HtmlContent } from "~/components/HtmlContent";
import { Link } from "~/components/Link";
import { useSource } from "~/hooks/useSource";
import { EditorWithDialog } from "~/subApps/builder/components/EditorWithDialog";
+import { InputField } from "~/subApps/builder/components/InputField";
import { RouteKey } from "~/subApps/builder/constants";
import {
ModalData,
useModalManager,
} from "~/subApps/builder/contexts/ModalManager";
-import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
import { DetailChoice } from "~/tools/js/Shared/containers/DetailChoice";
import { toastMessageActions } from "../../../../Shared/actions";
@@ -195,7 +195,7 @@ class CustomBackgroundManager extends React.PureComponent<
@@ -204,7 +204,7 @@ class CustomBackgroundManager extends React.PureComponent<
@@ -471,35 +471,35 @@ class DescriptionManage extends React.PureComponent
{
dispatch(characterActions.traitSet(traitType, content));
};
- handleHairChange = (value: string): void => {
+ handleHairChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.hairSet(value));
+ dispatch(characterActions.hairSet(e.target.value));
};
- handleSkinChange = (value: string): void => {
+ handleSkinChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.skinSet(value));
+ dispatch(characterActions.skinSet(e.target.value));
};
- handleEyesChange = (value: string): void => {
+ handleEyesChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.eyesSet(value));
+ dispatch(characterActions.eyesSet(e.target.value));
};
- handleHeightChange = (value: string): void => {
+ handleHeightChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.heightSet(value));
+ dispatch(characterActions.heightSet(e.target.value));
};
- handleWeightChange = (value: string): void => {
+ handleWeightChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- let parsedValue = HelperUtils.parseInputInt(value);
+ let parsedValue = this.handleTransformValueToNumberOnBlur(e.target.value);
dispatch(characterActions.weightSet(parsedValue));
};
- handleAgeChange = (value: string): void => {
+ handleAgeChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- let parsedValue = HelperUtils.parseInputInt(value);
+ let parsedValue = this.handleTransformValueToNumberOnBlur(e.target.value);
dispatch(characterActions.ageSet(parsedValue));
};
@@ -517,9 +517,9 @@ class DescriptionManage extends React.PureComponent {
return clampedValue;
};
- handleGenderChange = (value: string): void => {
+ handleGenderChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.genderSet(value));
+ dispatch(characterActions.genderSet(e.target.value));
};
handleBackgroundChangePromise = (
@@ -608,9 +608,9 @@ class DescriptionManage extends React.PureComponent {
dispatch(characterActions.lifestyleSet(HelperUtils.parseInputInt(value)));
};
- handleFaithChange = (value: string): void => {
+ handleFaithChange = (e: FocusEvent): void => {
const { dispatch } = this.props;
- dispatch(characterActions.faithSet(value));
+ dispatch(characterActions.faithSet(e.target.value));
};
handleCustomBackgroundSave = (properties: any): void => {
@@ -716,7 +716,7 @@ class DescriptionManage extends React.PureComponent {
{
) : (
)}
@@ -1325,17 +1325,17 @@ class DescriptionManage extends React.PureComponent {
{alignmentDescriptionNode}
-
}
- maxLength={512}
/>
@@ -1367,47 +1367,52 @@ class DescriptionManage extends React.PureComponent
{
]}
variant="paper"
>
-
-
-
-
-
-
- {
this.forceUpdate();
}}
/>
- {
- premadeInfo.definition.longDescription = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.longDescription = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.shortDescription = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.shortDescription = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.imageUrl = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.imageUrl = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.imageAltText = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.imageAltText = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.mobileImageUrl = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.mobileImageUrl = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.mobileImageAccessibility = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.mobileImageAccessibility =
+ e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
- {
- premadeInfo.definition.themeColor = value;
+ inputProps={inputAttributes}
+ onBlur={(e: FocusEvent) => {
+ premadeInfo.definition.themeColor = e.target.value;
this.handlePremadeInfoChanged(premadeInfo);
}}
/>
diff --git a/ddb_main/tools/js/Shared/components/ActionDetail/ActionDetail.tsx b/ddb_main/tools/js/Shared/components/ActionDetail/ActionDetail.tsx
index 0331b19..d47469c 100644
--- a/ddb_main/tools/js/Shared/components/ActionDetail/ActionDetail.tsx
+++ b/ddb_main/tools/js/Shared/components/ActionDetail/ActionDetail.tsx
@@ -62,8 +62,6 @@ interface Props {
theme?: CharacterTheme;
}
-const infoItemProps = { role: "listItem", inline: true };
-
export const ActionDetail = ({
showCustomize = true,
largePoolMinAmount = 11,
@@ -79,6 +77,8 @@ export const ActionDetail = ({
theme,
inventoryLookup,
}: Props) => {
+ const infoItemProps = { role: "listItem", inline: true };
+
const handleRemoveCustomizations = () => {
if (onCustomizationsRemove) {
onCustomizationsRemove();
@@ -427,17 +427,17 @@ export const ActionDetail = ({
)}
{attackSubtypeId && (
-
+
{ActionUtils.getAttackSubtypeName(action)}
)}
{requiresAttackRoll && (
-
+
)}
{requiresSavingThrow && (
-
+
{saveStateId !== null
? RuleDataUtils.getStatNameById(saveStateId, ruleData)
: ""}{" "}
@@ -446,14 +446,14 @@ export const ActionDetail = ({
)}
{renderDamageProperties()}
{requiresAttackRoll && (
-
+
{statId === null
? "--"
: RuleDataUtils.getStatNameById(statId, ruleData)}
)}
{rangeAreas.length > 0 && (
-
+
{rangeAreas.map((node, idx) => (
{node}
@@ -462,8 +462,16 @@ export const ActionDetail = ({
))}
)}
- {isProficient && Yes }
- {notes && {notes} }
+ {isProficient && (
+
+ Yes
+
+ )}
+ {notes && (
+
+ {notes}
+
+ )}
);
};
diff --git a/ddb_main/tools/js/Shared/components/SpellManager/SpellManager.tsx b/ddb_main/tools/js/Shared/components/SpellManager/SpellManager.tsx
index 993dc06..7001c4d 100644
--- a/ddb_main/tools/js/Shared/components/SpellManager/SpellManager.tsx
+++ b/ddb_main/tools/js/Shared/components/SpellManager/SpellManager.tsx
@@ -92,7 +92,7 @@ export default function SpellManagerContainer({
const getData = useCallback(
async function getData() {
- let classSpellMap = await spellsManager.getSpellShoppe();
+ let classSpellMap = await spellsManager.getSpellShoppe(true);
if (!classSpellMap[charClassId] || shouldFetch) {
classSpellMap = await spellsManager.getSpellShoppe(true);
}
diff --git a/ddb_main/tools/js/Shared/components/common/FormInputField/FormInputField.tsx b/ddb_main/tools/js/Shared/components/common/FormInputField/FormInputField.tsx
deleted file mode 100644
index 1c7b293..0000000
--- a/ddb_main/tools/js/Shared/components/common/FormInputField/FormInputField.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import clsx from "clsx";
-import {
- ChangeEvent,
- FC,
- FocusEvent,
- HTMLAttributes,
- useEffect,
- useRef,
- useState,
-} from "react";
-import { v4 as uuidv4 } from "uuid";
-
-import { useMaxLengthErrorHandling } from "~/hooks/useErrorHandling/useMaxLengthErrorHandling";
-
-export interface FormInputFieldProps
- extends Omit<
- HTMLAttributes
,
- "onChange" | "onBlur" | "onFocus"
- > {
- label: string;
- type?: string;
- placeholder?: string;
- initialValue?: string | number | null;
- inputAttributes?: HTMLAttributes;
- maxLength?: number;
- maxLengthErrorMsg?: string;
- variant?: "default" | "small";
- // Handlers
- onChange?: (value: string | number | null) => void;
- onBlur?: (value: string | number | null) => void;
- onFocus?: (value: string | number | null) => void;
- transformValueOnChange?: (value: string) => string | number | null;
- transformValueOnBlur?: (value: string) => string | number | null;
- transformValueOnFocus?: (value: string) => string | number | null;
-}
-
-export const FormInputField: FC = ({
- label, // req
- type = "text",
- placeholder = "",
- initialValue = "",
- inputAttributes = {},
- maxLength,
- maxLengthErrorMsg = "",
- ...handlers
-}) => {
- const prevInitialValue = useRef(initialValue);
- const initialLengthErrorState = maxLength
- ? (initialValue?.toString().length ?? 0) >= maxLength
- : false;
- const { MaxLengthErrorMessage, hideError, handleMaxLengthErrorMsg } =
- useMaxLengthErrorHandling(
- initialLengthErrorState,
- maxLength ?? null,
- maxLengthErrorMsg
- );
- const guid = `FIF_${uuidv4()}`;
- const [value, setValue] = useState(initialValue);
-
- const handleOnChange = (evt: ChangeEvent): void => {
- const { onChange = null, transformValueOnChange = null } = handlers;
- const { value: targetElementValue } = evt.target;
- const value = transformValueOnChange
- ? transformValueOnChange?.(targetElementValue)
- : targetElementValue;
-
- onChange?.(value);
- setValue(value);
- if (value !== null) {
- handleMaxLengthErrorMsg(value.toString());
- }
- };
-
- const handleOnBlur = (evt: FocusEvent) => {
- const { onBlur = null, transformValueOnBlur = null } = handlers;
- const { value: targetElementValue } = evt.target;
- const value = transformValueOnBlur
- ? transformValueOnBlur?.(targetElementValue)
- : targetElementValue;
-
- onBlur?.(value);
- if (value !== targetElementValue) {
- setValue(value);
- }
- // Always hide any length warnings when leaving the field since you can't go over the limit anyways
- hideError();
- };
-
- const handleOnFocus = (evt: FocusEvent) => {
- const { onFocus = null, transformValueOnFocus = null } = handlers;
- const { value: targetElementValue } = evt.target;
- const value = transformValueOnFocus
- ? transformValueOnFocus?.(targetElementValue)
- : targetElementValue;
-
- onFocus?.(value);
- if (value !== targetElementValue) {
- setValue(value);
- }
- if (value !== null) {
- handleMaxLengthErrorMsg(value.toString());
- }
- };
-
- useEffect(() => {
- if (initialValue !== prevInitialValue.current) {
- setValue(initialValue ?? "");
- prevInitialValue.current = initialValue;
- }
- }, [initialValue]);
-
- return (
-
-
-
- {label}
-
-
-
-
-
-
-
- );
-};
diff --git a/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/PrerequisiteFailureSummary.tsx b/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/PrerequisiteFailureSummary.tsx
deleted file mode 100644
index fcf3f95..0000000
--- a/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/PrerequisiteFailureSummary.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import clsx from "clsx";
-import * as React from "react";
-
-import { PrerequisiteFailure } from "@dndbeyond/character-rules-engine/es";
-
-interface Props {
- className: string;
- failures: Array>;
-}
-
-export default class PrerequisiteFailureSummary extends React.PureComponent<
- Props,
- {}
-> {
- static defaultProps = {
- className: "",
- };
-
- render() {
- const { failures, className } = this.props;
-
- let strings: string = failures
- .map((failure) =>
- failure
- .map((failureAnd) => failureAnd.data.requiredDescription)
- .reduce((acc, desc, idx) => {
- let connector: string = "";
- if (idx < failure.length - 2) {
- connector = ", ";
- } else if (idx < failure.length - 1) {
- connector = " and ";
- }
- acc += `${desc}${connector}`;
- return acc;
- }, "")
- )
- .join(" or ");
- return (
-
- Missing: {strings.length > 0 ? strings : "Unknown"}
-
- );
- }
-}
diff --git a/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/index.ts b/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/index.ts
deleted file mode 100644
index 58fa506..0000000
--- a/ddb_main/tools/js/smartComponents/PrerequisiteFailureSummary/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import PrerequisiteFailureSummary from "./PrerequisiteFailureSummary";
-
-export default PrerequisiteFailureSummary;
-export { PrerequisiteFailureSummary };
diff --git a/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/HpManageModal/styles.module.css?c97e b/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/HpManageModal/styles.module.css?c97e
index 5c52ef2..b853186 100644
--- a/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/HpManageModal/styles.module.css?c97e
+++ b/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/HpManageModal/styles.module.css?c97e
@@ -1,2 +1,2 @@
// extracted by mini-css-extract-plugin
-export default {"hpManageModal":"styles_hpManageModal__jXAcr","total":"styles_total__1a5fd","totalLabel":"styles_totalLabel__K0+aD","totalValue":"styles_totalValue__wZrWG","controls":"styles_controls__EvlpE","baseHp":"styles_baseHp__cA4rA","baseHpLabel":"styles_baseHpLabel__nc41x","baseHpValue":"styles_baseHpValue__tBbMl","hpSources":"styles_hpSources__lqYJK","info":"styles_info__Gkkvg","help":"styles_help__JapXf","infoGroup":"styles_infoGroup__IBi4u","infoLabel":"styles_infoLabel__jjH0g"};
\ No newline at end of file
+export default {"hpManageModal":"styles_hpManageModal__jXAcr","total":"styles_total__1a5fd","totalLabel":"styles_totalLabel__K0+aD","totalValue":"styles_totalValue__wZrWG","controls":"styles_controls__EvlpE","baseHp":"styles_baseHp__cA4rA","baseHpLabel":"styles_baseHpLabel__nc41x","baseHpValue":"styles_baseHpValue__tBbMl","hpSources":"styles_hpSources__lqYJK","info":"styles_info__Gkkvg","help":"styles_help__JapXf","infoGroup":"styles_infoGroup__IBi4u","infoLabel":"styles_infoLabel__jjH0g","inputField":"styles_inputField__PbrUj"};
\ No newline at end of file
diff --git a/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/InputField/styles.module.css?ec81 b/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/InputField/styles.module.css?ec81
new file mode 100644
index 0000000..153c76b
--- /dev/null
+++ b/ddb_main/webpack:/@dndbeyond/character-app/src/subApps/builder/components/InputField/styles.module.css?ec81
@@ -0,0 +1,2 @@
+// extracted by mini-css-extract-plugin
+export default {"label":"styles_label__FtMSp","input":"styles_input__2OhV7","error":"styles_error__kxBCf"};
\ No newline at end of file
diff --git a/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/styles.module.css?bb47 b/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/styles.module.css?bb47
new file mode 100644
index 0000000..1f7545b
--- /dev/null
+++ b/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/components/AbilityScoreManagerManual/styles.module.css?bb47
@@ -0,0 +1,2 @@
+// extracted by mini-css-extract-plugin
+export default {"input":"styles_input__YM9-A"};
\ No newline at end of file
diff --git a/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/containers/pages/DescriptionManage/styles.module.css?cacc b/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/containers/pages/DescriptionManage/styles.module.css?cacc
index 198e036..6e8e485 100644
--- a/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/containers/pages/DescriptionManage/styles.module.css?cacc
+++ b/ddb_main/webpack:/@dndbeyond/character-app/src/tools/js/CharacterBuilder/containers/pages/DescriptionManage/styles.module.css?cacc
@@ -1,2 +1,2 @@
// extracted by mini-css-extract-plugin
-export default {"accordionGroup":"styles_accordionGroup__ijLDx"};
\ No newline at end of file
+export default {"accordionGroup":"styles_accordionGroup__ijLDx","inputField":"styles_inputField__WePfs"};
\ No newline at end of file