New source found from dndbeyond.com
This commit is contained in:
parent
451d940294
commit
0b403376c5
@ -2,7 +2,6 @@ import clsx from "clsx";
|
|||||||
import { FC, HTMLAttributes, useEffect, useState } from "react";
|
import { FC, HTMLAttributes, useEffect, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
|
||||||
import {
|
import {
|
||||||
SpellUtils,
|
SpellUtils,
|
||||||
EntityUtils,
|
EntityUtils,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
characterEnvSelectors,
|
characterEnvSelectors,
|
||||||
} from "@dndbeyond/character-rules-engine/es";
|
} from "@dndbeyond/character-rules-engine/es";
|
||||||
|
|
||||||
|
import { Tooltip } from "~/components/Tooltip";
|
||||||
import { useCharacterTheme } from "~/contexts/CharacterTheme";
|
import { useCharacterTheme } from "~/contexts/CharacterTheme";
|
||||||
import { useUnpropagatedClick } from "~/hooks/useUnpropagatedClick";
|
import { useUnpropagatedClick } from "~/hooks/useUnpropagatedClick";
|
||||||
|
|
||||||
@ -77,23 +77,29 @@ export const SpellName: FC<Props> = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{showExpandedType && expandedInfoText !== "" && (
|
{showExpandedType && expandedInfoText !== "" && (
|
||||||
<Tooltip
|
<>
|
||||||
title={expandedInfoText}
|
<span
|
||||||
|
data-tooltip-id="expandedInfoText"
|
||||||
|
data-tooltip-content={expandedInfoText}
|
||||||
className={styles.expanded}
|
className={styles.expanded}
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</Tooltip>
|
</span>
|
||||||
|
<Tooltip id="expandedInfoText" />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{SpellUtils.getName(spell)}
|
{SpellUtils.getName(spell)}
|
||||||
{SpellUtils.isCustomized(spell) && (
|
{SpellUtils.isCustomized(spell) && (
|
||||||
<Tooltip
|
<>
|
||||||
title="Spell is Customized"
|
<span
|
||||||
|
data-tooltip-id="spellIsCustomized"
|
||||||
|
data-tooltip-content="Spell is Customized"
|
||||||
className={styles.customized}
|
className={styles.customized}
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
>
|
>
|
||||||
*
|
*
|
||||||
</Tooltip>
|
</span>
|
||||||
|
<Tooltip id="spellIsCustomized" />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{showIcons && SpellUtils.getConcentration(spell) && (
|
{showIcons && SpellUtils.getConcentration(spell) && (
|
||||||
<ConcentrationIcon
|
<ConcentrationIcon
|
||||||
|
@ -216,9 +216,6 @@ export const useCharacterEngine = () => ({
|
|||||||
featOption: useSelector(s.getFeatOptionLookup),
|
featOption: useSelector(s.getFeatOptionLookup),
|
||||||
featChoice: useSelector(s.getFeatChoiceLookup),
|
featChoice: useSelector(s.getFeatChoiceLookup),
|
||||||
gearWeaponItems: useSelector(s.getGearWeaponItems),
|
gearWeaponItems: useSelector(s.getGearWeaponItems),
|
||||||
globalBackgroundSpellListIds: useSelector(s.getGlobalBackgroundSpellListIds),
|
|
||||||
globalRaceSpellListIds: useSelector(s.getGlobalRaceSpellListIds),
|
|
||||||
globalSpellListIds: useSelector(s.getGlobalSpellListIds),
|
|
||||||
hasInitiativeAdvantage: useSelector(s.getHasInitiativeAdvantage),
|
hasInitiativeAdvantage: useSelector(s.getHasInitiativeAdvantage),
|
||||||
hasMaxAttunedItems: useSelector(s.hasMaxAttunedItems),
|
hasMaxAttunedItems: useSelector(s.hasMaxAttunedItems),
|
||||||
hasSpells: useSelector(s.hasSpells),
|
hasSpells: useSelector(s.hasSpells),
|
||||||
|
@ -450,3 +450,6 @@ export function getFeatListContracts(contract) {
|
|||||||
var _a, _b;
|
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 : [];
|
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;
|
||||||
|
}
|
||||||
|
@ -218,7 +218,7 @@ export function getDataOriginRefName(ref, refData, defaultValue, tryParent = fal
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DataOriginTypeEnum.UNKNOWN: {
|
case DataOriginTypeEnum.UNKNOWN: {
|
||||||
extraDisplay = defaultValue !== null && defaultValue !== void 0 ? defaultValue : 'No Origin';
|
extraDisplay = defaultValue !== null && defaultValue !== void 0 ? defaultValue : 'Unknown Origin';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -281,3 +281,7 @@ export function getDefinitionRepeatableParentId(feat) {
|
|||||||
export function getRepeatableParentId(feat) {
|
export function getRepeatableParentId(feat) {
|
||||||
return getDefinitionRepeatableParentId(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 : [];
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ import { ModifierGenerators } from '../Modifier';
|
|||||||
import { OptionAccessors, OptionGenerators } from '../Option';
|
import { OptionAccessors, OptionGenerators } from '../Option';
|
||||||
import { RaceAccessors } from '../Race';
|
import { RaceAccessors } from '../Race';
|
||||||
import { SpellGenerators } from '../Spell';
|
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
|
* @param feat
|
||||||
@ -342,3 +342,20 @@ export function generateDataOriginPairedFeats(feats) {
|
|||||||
}
|
}
|
||||||
return lookup;
|
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;
|
||||||
|
}
|
||||||
|
@ -79,6 +79,13 @@ export function getDescription(prerequisiteGrouping) {
|
|||||||
export function getSubType(prerequisite) {
|
export function getSubType(prerequisite) {
|
||||||
return prerequisite.subType;
|
return prerequisite.subType;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param prerequisite
|
||||||
|
*/
|
||||||
|
export function getShouldExclude(prerequisite) {
|
||||||
|
return prerequisite.shouldExclude;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AbilityAccessors } from '../Ability';
|
import { AbilityAccessors } from '../Ability';
|
||||||
import { FormatUtils } from '../Format';
|
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 { PrerequisiteSubTypeEnum, PrerequisiteTypeEnum } from './constants';
|
||||||
import { validatePrerequisite } from './validators';
|
import { validatePrerequisite } from './validators';
|
||||||
/**
|
/**
|
||||||
@ -13,19 +13,19 @@ export function getPrerequisiteFailure(prerequisite, prerequisiteData) {
|
|||||||
case PrerequisiteTypeEnum.ABILITY_SCORE:
|
case PrerequisiteTypeEnum.ABILITY_SCORE:
|
||||||
return getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteData);
|
||||||
case PrerequisiteTypeEnum.PROFICIENCY:
|
case PrerequisiteTypeEnum.PROFICIENCY:
|
||||||
return getPrerequisiteFailureProficiency(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureProficiency(prerequisite);
|
||||||
case PrerequisiteTypeEnum.SPECIES:
|
case PrerequisiteTypeEnum.SPECIES:
|
||||||
return getPrerequisiteFailureRace(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureRace(prerequisite);
|
||||||
case PrerequisiteTypeEnum.SIZE:
|
case PrerequisiteTypeEnum.SIZE:
|
||||||
return getPrerequisiteFailureSize(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureSize(prerequisite);
|
||||||
case PrerequisiteTypeEnum.SPECIES_OPTION:
|
case PrerequisiteTypeEnum.SPECIES_OPTION:
|
||||||
return getPrerequisiteFailureSubrace(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureSubrace(prerequisite);
|
||||||
case PrerequisiteTypeEnum.LEVEL:
|
case PrerequisiteTypeEnum.LEVEL:
|
||||||
return getPrerequisiteFailureLevel(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureLevel(prerequisite);
|
||||||
case PrerequisiteTypeEnum.CLASS:
|
case PrerequisiteTypeEnum.CLASS:
|
||||||
return getPrerequisiteFailureClass(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureClass(prerequisite);
|
||||||
case PrerequisiteTypeEnum.FEAT:
|
case PrerequisiteTypeEnum.FEAT:
|
||||||
return getPrerequisiteFailureFeat(prerequisite, prerequisiteData);
|
return getPrerequisiteFailureFeat(prerequisite);
|
||||||
case PrerequisiteTypeEnum.CLASS_FEATURE:
|
case PrerequisiteTypeEnum.CLASS_FEATURE:
|
||||||
return getPrerequisiteFailureClassFeature(prerequisite);
|
return getPrerequisiteFailureClassFeature(prerequisite);
|
||||||
case PrerequisiteTypeEnum.CUSTOM_VALUE:
|
case PrerequisiteTypeEnum.CUSTOM_VALUE:
|
||||||
@ -39,7 +39,7 @@ export function getPrerequisiteFailure(prerequisite, prerequisiteData) {
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureLevel(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureLevel(prerequisite) {
|
||||||
let requiredDescription = '';
|
let requiredDescription = '';
|
||||||
switch (getSubType(prerequisite)) {
|
switch (getSubType(prerequisite)) {
|
||||||
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
|
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
|
||||||
@ -56,6 +56,7 @@ export function getPrerequisiteFailureLevel(prerequisite, prerequisiteData) {
|
|||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription,
|
requiredDescription,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -81,6 +82,7 @@ export function getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteDat
|
|||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: `${getFriendlySubtypeName(prerequisite)} ${getValue(prerequisite)}+`,
|
requiredDescription: `${getFriendlySubtypeName(prerequisite)} ${getValue(prerequisite)}+`,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -88,13 +90,14 @@ export function getPrerequisiteFailureAbilityScore(prerequisite, prerequisiteDat
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureProficiency(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureProficiency(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.PROFICIENCY,
|
type: PrerequisiteTypeEnum.PROFICIENCY,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Proficiency`,
|
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Proficiency`,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -102,13 +105,14 @@ export function getPrerequisiteFailureProficiency(prerequisite, prerequisiteData
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureRace(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureRace(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.SPECIES,
|
type: PrerequisiteTypeEnum.SPECIES,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: getFriendlySubtypeName(prerequisite),
|
requiredDescription: getFriendlySubtypeName(prerequisite),
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -116,13 +120,14 @@ export function getPrerequisiteFailureRace(prerequisite, prerequisiteData) {
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureSubrace(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureSubrace(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.SPECIES_OPTION,
|
type: PrerequisiteTypeEnum.SPECIES_OPTION,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: getFriendlySubtypeName(prerequisite),
|
requiredDescription: getFriendlySubtypeName(prerequisite),
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -130,13 +135,14 @@ export function getPrerequisiteFailureSubrace(prerequisite, prerequisiteData) {
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureSize(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureSize(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.SIZE,
|
type: PrerequisiteTypeEnum.SIZE,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Size`,
|
requiredDescription: `${getFriendlySubtypeName(prerequisite)} Size`,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -144,13 +150,14 @@ export function getPrerequisiteFailureSize(prerequisite, prerequisiteData) {
|
|||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureClass(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureClass(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.CLASS,
|
type: PrerequisiteTypeEnum.CLASS,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
|
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export const getPrerequisiteFailureClassFeature = (prerequisite) => ({
|
export const getPrerequisiteFailureClassFeature = (prerequisite) => ({
|
||||||
@ -159,19 +166,21 @@ export const getPrerequisiteFailureClassFeature = (prerequisite) => ({
|
|||||||
requiredChoice: prerequisite.friendlySubTypeName,
|
requiredChoice: prerequisite.friendlySubTypeName,
|
||||||
requiredDescription: prerequisite.friendlySubTypeName,
|
requiredDescription: prerequisite.friendlySubTypeName,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param prerequisite
|
* @param prerequisite
|
||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function getPrerequisiteFailureFeat(prerequisite, prerequisiteData) {
|
export function getPrerequisiteFailureFeat(prerequisite) {
|
||||||
return {
|
return {
|
||||||
type: PrerequisiteTypeEnum.FEAT,
|
type: PrerequisiteTypeEnum.FEAT,
|
||||||
data: {
|
data: {
|
||||||
requiredChoice: getFriendlySubtypeName(prerequisite),
|
requiredChoice: getFriendlySubtypeName(prerequisite),
|
||||||
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
|
requiredDescription: `${getFriendlySubtypeName(prerequisite)}`,
|
||||||
},
|
},
|
||||||
|
shouldExclude: !!getShouldExclude(prerequisite),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -198,14 +207,3 @@ export function getPrerequisiteGroupingFailures(prerequisiteGrouping, prerequisi
|
|||||||
}
|
}
|
||||||
return groupingFailures.filter((group) => group.length);
|
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);
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
@ -2,9 +2,8 @@ import { AbilityAccessors } from '../Ability';
|
|||||||
import { ClassAccessors } from '../Class';
|
import { ClassAccessors } from '../Class';
|
||||||
import { HelperUtils } from '../Helper';
|
import { HelperUtils } from '../Helper';
|
||||||
import { RaceAccessors } from '../Race';
|
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 { PrerequisiteSubTypeEnum, PrerequisiteTypeEnum } from './constants';
|
||||||
import { getPrereqsByType } from './utils';
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param prerequisiteGrouping
|
* @param prerequisiteGrouping
|
||||||
@ -63,9 +62,12 @@ export function validatePrerequisite(prerequisite, prerequisiteData) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
export function validatePrerequisiteClassFeature(prerequisite, prerequisiteData) {
|
export function validatePrerequisiteClassFeature(prerequisite, prerequisiteData) {
|
||||||
|
const shouldExclude = getShouldExclude(prerequisite);
|
||||||
if (!prerequisite.entityId)
|
if (!prerequisite.entityId)
|
||||||
return true;
|
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
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function validatePrerequisiteLevel(prerequisite, prerequisiteData) {
|
export function validatePrerequisiteLevel(prerequisite, prerequisiteData) {
|
||||||
|
const shouldExclude = getShouldExclude(prerequisite);
|
||||||
switch (getSubType(prerequisite)) {
|
switch (getSubType(prerequisite)) {
|
||||||
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
|
case PrerequisiteSubTypeEnum.CHARACTER_LEVEL:
|
||||||
const value = getValue(prerequisite);
|
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:
|
default:
|
||||||
// not implemented
|
// not implemented
|
||||||
}
|
}
|
||||||
@ -101,6 +106,12 @@ export function validatePrerequisiteAbilityScore(prerequisite, prerequisiteData)
|
|||||||
if (value === null) {
|
if (value === null) {
|
||||||
return true;
|
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;
|
return abilityScore >= value;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -109,7 +120,10 @@ export function validatePrerequisiteAbilityScore(prerequisite, prerequisiteData)
|
|||||||
* @param prerequisiteData
|
* @param prerequisiteData
|
||||||
*/
|
*/
|
||||||
export function validatePrerequisiteProficiency(prerequisite, 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) {
|
if (!prerequisiteData.race) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const shouldExclude = getShouldExclude(prerequisite);
|
||||||
// check if race is a base race
|
// check if race is a base race
|
||||||
if (RaceAccessors.getBaseRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
|
if (RaceAccessors.getBaseRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
|
||||||
RaceAccessors.getBaseRaceTypeId(prerequisiteData.race) === getEntityTypeId(prerequisite)) {
|
RaceAccessors.getBaseRaceTypeId(prerequisiteData.race) === getEntityTypeId(prerequisite)) {
|
||||||
return true;
|
return !shouldExclude;
|
||||||
}
|
}
|
||||||
// check if race is specific race
|
// check if race is specific race
|
||||||
return validatePrerequisiteEntityRace(prerequisite, prerequisiteData);
|
return validatePrerequisiteEntityRace(prerequisite, prerequisiteData);
|
||||||
@ -149,8 +164,11 @@ function validatePrerequisiteEntityRace(prerequisite, prerequisiteData) {
|
|||||||
if (!prerequisiteData.race) {
|
if (!prerequisiteData.race) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return (RaceAccessors.getEntityRaceId(prerequisiteData.race) === getEntityId(prerequisite) &&
|
const shouldExclude = getShouldExclude(prerequisite);
|
||||||
RaceAccessors.getEntityRaceTypeId(prerequisiteData.race) === getEntityTypeId(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) {
|
if (!sizeInfo) {
|
||||||
return true;
|
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) {
|
if (entityId === null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return Object.values(prerequisiteData.baseClassLookup).some((charClass) => ClassAccessors.getId(charClass) === entityId);
|
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);
|
||||||
* @param prerequisiteGrouping
|
return shouldExclude ? !hasClass : hasClass;
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -206,5 +213,8 @@ export function validatePrerequisiteFeat(prerequisite, prerequisiteData) {
|
|||||||
if (entityId === null) {
|
if (entityId === null) {
|
||||||
return true;
|
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;
|
||||||
}
|
}
|
||||||
|
@ -638,7 +638,7 @@ export function generateActiveCharacterSpells(spells, inventoryInfusionLookup, c
|
|||||||
* @param optionalClassFeatures
|
* @param optionalClassFeatures
|
||||||
* @param definitionPool
|
* @param definitionPool
|
||||||
*/
|
*/
|
||||||
export function generateSpellListDataOriginLookup(race, classes, background, optionalOrigins, optionalClassFeatures, definitionPool) {
|
export function generateSpellListDataOriginLookup(race, classes, background, optionalOrigins, optionalClassFeatures, definitionPool, feats) {
|
||||||
let lookup = {};
|
let lookup = {};
|
||||||
if (race !== null) {
|
if (race !== null) {
|
||||||
RaceAccessors.getDefinitionRacialTraits(race).forEach((feature) => {
|
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) => {
|
optionalOrigins.forEach((optionalOrigin) => {
|
||||||
const definitionKey = OptionalOriginAccessors.getDefinitionKey(optionalOrigin);
|
const definitionKey = OptionalOriginAccessors.getDefinitionKey(optionalOrigin);
|
||||||
if (!definitionKey) {
|
if (!definitionKey) {
|
||||||
@ -697,6 +703,6 @@ export function generateSpellListDataOriginLookup(race, classes, background, opt
|
|||||||
* @param raceSpellListIds
|
* @param raceSpellListIds
|
||||||
* @param backgroundSpellListIds
|
* @param backgroundSpellListIds
|
||||||
*/
|
*/
|
||||||
export function generateGlobalSpellListIds(raceSpellListIds, backgroundSpellListIds) {
|
export function generateGlobalSpellListIds(raceSpellListIds, backgroundSpellListIds, featSpellListIds) {
|
||||||
return [...raceSpellListIds, ...backgroundSpellListIds];
|
return [...raceSpellListIds, ...backgroundSpellListIds, ...featSpellListIds];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
import { characterActions } from "../actions";
|
import { characterActions } from "../actions";
|
||||||
import { ChoiceUtils } from "../engine/Choice";
|
import { ChoiceUtils } from "../engine/Choice";
|
||||||
import { DisplayConfigurationTypeEnum } from "../engine/Core";
|
import { DisplayConfigurationTypeEnum } from "../engine/Core";
|
||||||
@ -70,10 +71,54 @@ export class FeatManager extends BaseManager {
|
|||||||
// user-facing categories from technical-only tags.
|
// user-facing categories from technical-only tags.
|
||||||
this.getCategories = () => FeatAccessors.getCategories(this.feat);
|
this.getCategories = () => FeatAccessors.getCategories(this.feat);
|
||||||
//Utils
|
//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 = () => {
|
this.getPrerequisiteFailures = () => {
|
||||||
const prerequisiteData = rulesEngineSelectors.getPrerequisiteData(this.state);
|
const prerequisiteData = rulesEngineSelectors.getPrerequisiteData(this.state);
|
||||||
return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData);
|
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.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat);
|
||||||
this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null;
|
this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null;
|
||||||
this.getDefinitionKey = () => {
|
this.getDefinitionKey = () => {
|
||||||
|
@ -144,7 +144,7 @@ function* filter(action) {
|
|||||||
*
|
*
|
||||||
* @param action
|
* @param action
|
||||||
*/
|
*/
|
||||||
function* executePostAction(action) {
|
export function* executePostAction(action) {
|
||||||
if (isPostAction(action.meta)) {
|
if (isPostAction(action.meta)) {
|
||||||
for (let i = 0; i < action.meta.postAction.type.length; i++) {
|
for (let i = 0; i < action.meta.postAction.type.length; i++) {
|
||||||
yield put({
|
yield put({
|
||||||
@ -184,7 +184,7 @@ function* executeSyncTransactionDeactivate(action, transactionInitiatorId) {
|
|||||||
*
|
*
|
||||||
* @param action
|
* @param action
|
||||||
*/
|
*/
|
||||||
function* executeApi(action) {
|
export function* executeApi(action) {
|
||||||
let apiLookup = {
|
let apiLookup = {
|
||||||
// ACTION
|
// ACTION
|
||||||
[types.ACTION_USE_SET]: apiShared.putCharacterActionLimitedUse,
|
[types.ACTION_USE_SET]: apiShared.putCharacterActionLimitedUse,
|
||||||
@ -392,7 +392,7 @@ function* executeApi(action) {
|
|||||||
yield call(apiRequest, apiPayload);
|
yield call(apiRequest, apiPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function* executeHandler(action) {
|
export function* executeHandler(action) {
|
||||||
let handlerLookup = {
|
let handlerLookup = {
|
||||||
//ACTION
|
//ACTION
|
||||||
[types.ACTION_CUSTOMIZATIONS_DELETE]: sagaHandlers.handleActionCustomizationsDelete,
|
[types.ACTION_CUSTOMIZATIONS_DELETE]: sagaHandlers.handleActionCustomizationsDelete,
|
||||||
|
@ -97,7 +97,7 @@ function* filter(action) {
|
|||||||
*
|
*
|
||||||
* @param action
|
* @param action
|
||||||
*/
|
*/
|
||||||
function* executePostAction(action) {
|
export function* executePostAction(action) {
|
||||||
if (isPostAction(action.meta)) {
|
if (isPostAction(action.meta)) {
|
||||||
for (let i = 0; i < action.meta.postAction.type.length; i++) {
|
for (let i = 0; i < action.meta.postAction.type.length; i++) {
|
||||||
yield put({
|
yield put({
|
||||||
@ -137,7 +137,7 @@ function* executeSyncTransactionDeactivate(action, transactionInitiatorId) {
|
|||||||
*
|
*
|
||||||
* @param action
|
* @param action
|
||||||
*/
|
*/
|
||||||
function* executeApi(action) {
|
export function* executeApi(action) {
|
||||||
let apiLookup = {
|
let apiLookup = {
|
||||||
//KNOWN_INFUSION
|
//KNOWN_INFUSION
|
||||||
[types.KNOWN_INFUSION_MAPPING_SET]: apiShared.putCharacterKnownInfusion,
|
[types.KNOWN_INFUSION_MAPPING_SET]: apiShared.putCharacterKnownInfusion,
|
||||||
@ -207,7 +207,7 @@ function* executeApi(action) {
|
|||||||
yield call(apiRequest, apiPayload);
|
yield call(apiRequest, apiPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function* executeHandler(action) {
|
export function* executeHandler(action) {
|
||||||
let handlerLookup = {
|
let handlerLookup = {
|
||||||
//KNOWN_INFUSION
|
//KNOWN_INFUSION
|
||||||
[types.KNOWN_INFUSION_MAPPING_CREATE]: sagaHandlers.handleKnownInfusionCreate,
|
[types.KNOWN_INFUSION_MAPPING_CREATE]: sagaHandlers.handleKnownInfusionCreate,
|
||||||
|
@ -68,6 +68,7 @@ export const getSpellListDataOriginLookup = createSelector([
|
|||||||
characterSelectors.getOptionalOrigins,
|
characterSelectors.getOptionalOrigins,
|
||||||
characterSelectors.getOptionalClassFeatures,
|
characterSelectors.getOptionalClassFeatures,
|
||||||
serviceDataSelectors.getDefinitionPool,
|
serviceDataSelectors.getDefinitionPool,
|
||||||
|
characterSelectors.getFeats,
|
||||||
], SpellGenerators.generateSpellListDataOriginLookup);
|
], SpellGenerators.generateSpellListDataOriginLookup);
|
||||||
/**
|
/**
|
||||||
* @returns {Record<string, Array<ActionContract>>}
|
* @returns {Record<string, Array<ActionContract>>}
|
||||||
@ -400,7 +401,11 @@ export const getGlobalBackgroundSpellListIds = createSelector([getBackgroundInfo
|
|||||||
/**
|
/**
|
||||||
* @returns {Array<number>}
|
* @returns {Array<number>}
|
||||||
*/
|
*/
|
||||||
export const getGlobalSpellListIds = createSelector([getGlobalRaceSpellListIds, getGlobalBackgroundSpellListIds], SpellGenerators.generateGlobalSpellListIds);
|
export const getGlobalFeatsSpellListIds = createSelector([getFeats], FeatGenerators.generateGlobalFeatsSpellListIds);
|
||||||
|
/**
|
||||||
|
* @returns {Array<number>}
|
||||||
|
*/
|
||||||
|
export const getGlobalSpellListIds = createSelector([getGlobalRaceSpellListIds, getGlobalBackgroundSpellListIds, getGlobalFeatsSpellListIds], SpellGenerators.generateGlobalSpellListIds);
|
||||||
/**
|
/**
|
||||||
* @returns {DecorationInfo}
|
* @returns {DecorationInfo}
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { FC, HTMLAttributes, useMemo, useState } from "react";
|
import { FC, FocusEvent, useMemo, useState } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -19,8 +19,8 @@ import {
|
|||||||
HP_BONUS_VALUE,
|
HP_BONUS_VALUE,
|
||||||
HP_OVERRIDE_MAX_VALUE,
|
HP_OVERRIDE_MAX_VALUE,
|
||||||
} from "~/subApps/sheet/constants";
|
} from "~/subApps/sheet/constants";
|
||||||
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
|
|
||||||
|
|
||||||
|
import { InputField } from "../InputField";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
export interface HpManageModalProps
|
export interface HpManageModalProps
|
||||||
@ -65,52 +65,55 @@ export const HpManageModal: FC<HpManageModalProps> = ({
|
|||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformBaseHp = (value: string): number => {
|
const handleBaseHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
let parsedNumber = HelperUtils.parseInputInt(
|
let parsedNumber = HelperUtils.parseInputInt(
|
||||||
value,
|
e.target.value,
|
||||||
RuleDataUtils.getMinimumHpTotal(ruleData)
|
RuleDataUtils.getMinimumHpTotal(ruleData)
|
||||||
);
|
);
|
||||||
return HelperUtils.clampInt(
|
|
||||||
|
const clampedValue = HelperUtils.clampInt(
|
||||||
parsedNumber,
|
parsedNumber,
|
||||||
RuleDataUtils.getMinimumHpTotal(ruleData),
|
RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||||
HP_BASE_MAX_VALUE
|
HP_BASE_MAX_VALUE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setBaseHp(clampedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformBonusHp = (value: string): number | null => {
|
const handleBonusHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
let parsedNumber = HelperUtils.parseInputInt(value);
|
let parsedNumber = HelperUtils.parseInputInt(e.target.value);
|
||||||
if (parsedNumber === null) {
|
|
||||||
return parsedNumber;
|
if (parsedNumber === null) return parsedNumber;
|
||||||
}
|
|
||||||
return HelperUtils.clampInt(
|
const clampedValue = HelperUtils.clampInt(
|
||||||
parsedNumber,
|
parsedNumber,
|
||||||
HP_BONUS_VALUE.MIN,
|
HP_BONUS_VALUE.MIN,
|
||||||
HP_BONUS_VALUE.MAX
|
HP_BONUS_VALUE.MAX
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setBonusHp(clampedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformOverrideHp = (value: string): number | null => {
|
const handleOverrideHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
let parsedNumber = HelperUtils.parseInputInt(value, null);
|
let parsedNumber = HelperUtils.parseInputInt(e.target.value, null);
|
||||||
|
|
||||||
if (parsedNumber === null) {
|
if (parsedNumber === null) {
|
||||||
|
setOverrideHp(null);
|
||||||
return parsedNumber;
|
return parsedNumber;
|
||||||
}
|
}
|
||||||
return HelperUtils.clampInt(
|
|
||||||
|
const clampedValue = HelperUtils.clampInt(
|
||||||
parsedNumber,
|
parsedNumber,
|
||||||
RuleDataUtils.getMinimumHpTotal(ruleData)
|
RuleDataUtils.getMinimumHpTotal(ruleData)
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const handleOverrideHpUpdate = (value: number | null): void => {
|
const clampedOverride = HelperUtils.clampInt(
|
||||||
const newOverride =
|
clampedValue,
|
||||||
value === null
|
|
||||||
? null
|
|
||||||
: HelperUtils.clampInt(
|
|
||||||
value,
|
|
||||||
RuleDataUtils.getMinimumHpTotal(ruleData),
|
RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||||
HP_OVERRIDE_MAX_VALUE
|
HP_OVERRIDE_MAX_VALUE
|
||||||
);
|
);
|
||||||
|
|
||||||
setOverrideHp(newOverride);
|
setOverrideHp(clampedOverride);
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalHp = useMemo(() => {
|
const totalHp = useMemo(() => {
|
||||||
@ -148,35 +151,33 @@ export const HpManageModal: FC<HpManageModalProps> = ({
|
|||||||
<span className={styles.baseHpValue}>{baseHp}</span>
|
<span className={styles.baseHpValue}>{baseHp}</span>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Rolled HP"
|
label="Rolled HP"
|
||||||
initialValue={baseHp}
|
initialValue={baseHp}
|
||||||
type="number"
|
type="number"
|
||||||
onBlur={(value) => setBaseHp(value as number)}
|
onBlur={handleBaseHp}
|
||||||
transformValueOnBlur={transformBaseHp}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="HP Modifier"
|
label="HP Modifier"
|
||||||
initialValue={bonusHp}
|
initialValue={bonusHp}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={"--"}
|
placeholder="--"
|
||||||
onBlur={(value) => setBonusHp(value as number)}
|
onBlur={handleBonusHp}
|
||||||
transformValueOnBlur={transformBonusHp}
|
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
inputAttributes={
|
className={styles.inputField}
|
||||||
{
|
|
||||||
min: RuleDataUtils.getMinimumHpTotal(ruleData),
|
|
||||||
max: HP_OVERRIDE_MAX_VALUE,
|
|
||||||
} as HTMLAttributes<HTMLInputElement>
|
|
||||||
}
|
|
||||||
label="Override HP"
|
label="Override HP"
|
||||||
initialValue={overrideHp}
|
initialValue={overrideHp}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={"--"}
|
placeholder={"--"}
|
||||||
onBlur={handleOverrideHpUpdate}
|
inputProps={{
|
||||||
transformValueOnBlur={transformOverrideHp}
|
min: RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||||
|
max: HP_OVERRIDE_MAX_VALUE,
|
||||||
|
}}
|
||||||
|
onBlur={handleOverrideHp}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
132
ddb_main/subApps/builder/components/InputField/InputField.tsx
Normal file
132
ddb_main/subApps/builder/components/InputField/InputField.tsx
Normal file
@ -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<HTMLInputElement> {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
autoComplete?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputFieldProps extends HTMLAttributes<HTMLInputElement> {
|
||||||
|
errorMessage?: string;
|
||||||
|
initialValue?: string | number | null;
|
||||||
|
label: string;
|
||||||
|
maxLength?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
type?: HTMLInputTypeAttribute;
|
||||||
|
inputProps?: InputProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputField: FC<InputFieldProps> = ({
|
||||||
|
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<HTMLInputElement>) => {
|
||||||
|
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<HTMLInputElement>) => {
|
||||||
|
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<HTMLInputElement>) => {
|
||||||
|
// 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 (
|
||||||
|
<div className={clsx([styles.inputField, className])} {...props}>
|
||||||
|
<label className={styles.label} htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className={styles.input}
|
||||||
|
value={value ?? ""}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
{...inputAttrs}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
{showError && <span className={styles.error}>{errorMessage}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -16,11 +16,11 @@ import {
|
|||||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||||
import { builderActions } from "~/tools/js/CharacterBuilder/actions";
|
import { builderActions } from "~/tools/js/CharacterBuilder/actions";
|
||||||
import { builderSelectors } from "~/tools/js/CharacterBuilder/selectors";
|
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 { PortraitManager } from "~/tools/js/Shared/containers/panes/DecoratePane";
|
||||||
import Tooltip from "~/tools/js/commonComponents/Tooltip";
|
import Tooltip from "~/tools/js/commonComponents/Tooltip";
|
||||||
import { CharacterAvatarPortrait } from "~/tools/js/smartComponents/CharacterAvatar";
|
import { CharacterAvatarPortrait } from "~/tools/js/smartComponents/CharacterAvatar";
|
||||||
|
|
||||||
|
import { InputField } from "../InputField";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||||
@ -56,7 +56,7 @@ export const PortraitName: FC<Props> = ({
|
|||||||
setCurrentSuggestedNames(suggestedNames);
|
setCurrentSuggestedNames(suggestedNames);
|
||||||
}, [suggestedNames]);
|
}, [suggestedNames]);
|
||||||
|
|
||||||
const handleNameFieldSet = (value: string | null): void => {
|
const handleNameFieldSet = (value: string) => {
|
||||||
let updatedName = value ?? "";
|
let updatedName = value ?? "";
|
||||||
|
|
||||||
// If the flag useDefaultCharacterName is true and value is falsy, set the name to the DefaultCharacterName
|
// If the flag useDefaultCharacterName is true and value is falsy, set the name to the DefaultCharacterName
|
||||||
@ -137,18 +137,16 @@ export const PortraitName: FC<Props> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Character Name"
|
label="Character Name"
|
||||||
onBlur={handleNameFieldSet}
|
|
||||||
initialValue={localValue}
|
initialValue={localValue}
|
||||||
inputAttributes={
|
maxLength={InputLimits.characterNameMaxLength}
|
||||||
{
|
errorMessage={CharacterNameLimitMsg}
|
||||||
|
onBlur={(e) => handleNameFieldSet(e.target.value)}
|
||||||
|
inputProps={{
|
||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
autoComplete: "off",
|
autoComplete: "off",
|
||||||
} as HTMLAttributes<HTMLInputElement>
|
}}
|
||||||
}
|
|
||||||
maxLength={InputLimits.characterNameMaxLength}
|
|
||||||
maxLengthErrorMsg={CharacterNameLimitMsg}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={styles.suggestionButton}
|
className={styles.suggestionButton}
|
||||||
|
@ -11,7 +11,6 @@ import Collapsible, {
|
|||||||
CollapsibleHeaderContent,
|
CollapsibleHeaderContent,
|
||||||
} from "~/tools/js/smartComponents/Collapsible";
|
} from "~/tools/js/smartComponents/Collapsible";
|
||||||
import { TodoIcon } from "~/tools/js/smartComponents/Icons";
|
import { TodoIcon } from "~/tools/js/smartComponents/Icons";
|
||||||
import PrerequisiteFailureSummary from "~/tools/js/smartComponents/PrerequisiteFailureSummary";
|
|
||||||
import { CharacterTheme } from "~/types";
|
import { CharacterTheme } from "~/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -110,10 +109,9 @@ export const Feat: FC<Props> = ({
|
|||||||
<Reference name={feat.getPrimarySourceName()} />
|
<Reference name={feat.getPrimarySourceName()} />
|
||||||
</span>
|
</span>
|
||||||
{showFailures && (
|
{showFailures && (
|
||||||
<PrerequisiteFailureSummary
|
<div className={styles.failures}>
|
||||||
failures={feat.getPrerequisiteFailures()}
|
{feat.getPrerequisiteFailuresText()}
|
||||||
className={styles.failures}
|
</div>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
import { FocusEvent } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbilityManager,
|
AbilityManager,
|
||||||
HelperUtils,
|
HelperUtils,
|
||||||
} from "@dndbeyond/character-rules-engine/es";
|
} 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 {
|
interface Props {
|
||||||
abilities: Array<AbilityManager>;
|
abilities: Array<AbilityManager>;
|
||||||
@ -16,14 +20,19 @@ export default function AbilityScoreManagerManual({
|
|||||||
minScore = 3,
|
minScore = 3,
|
||||||
maxScore = 18,
|
maxScore = 18,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const handleTransformValueOnBlur = (value: string): number | null => {
|
const handleBlur = (
|
||||||
const parsedValue = HelperUtils.parseInputInt(value);
|
e: FocusEvent<HTMLInputElement>,
|
||||||
|
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;
|
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);
|
clampedValue = HelperUtils.clampInt(parsedValue, minScore, maxScore);
|
||||||
}
|
// Set the score change from the clampped value
|
||||||
|
abilityScore.handleScoreChange(clampedValue);
|
||||||
return clampedValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -35,11 +44,17 @@ export default function AbilityScoreManagerManual({
|
|||||||
key={abilityScore.getId()}
|
key={abilityScore.getId()}
|
||||||
data-stat-id={abilityScore.getId()}
|
data-stat-id={abilityScore.getId()}
|
||||||
>
|
>
|
||||||
<FormInputField
|
<InputField
|
||||||
transformValueOnBlur={handleTransformValueOnBlur}
|
className={styles.input}
|
||||||
onBlur={(value) => abilityScore.handleScoreChange(Number(value))}
|
type="number"
|
||||||
label={abilityScore.getLabel() ?? ""}
|
label={abilityScore.getLabel() ?? ""}
|
||||||
initialValue={abilityScore.getBaseScore()}
|
initialValue={abilityScore.getBaseScore()}
|
||||||
|
inputProps={{
|
||||||
|
min: minScore,
|
||||||
|
max: maxScore,
|
||||||
|
autoComplete: "off",
|
||||||
|
}}
|
||||||
|
onBlur={(e) => handleBlur(e, abilityScore)}
|
||||||
/>
|
/>
|
||||||
<div className="ability-score-manager-stat-total">
|
<div className="ability-score-manager-stat-total">
|
||||||
Total: {abilityScore.getTotalScore() ?? "--"}
|
Total: {abilityScore.getTotalScore() ?? "--"}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import axios, { Canceler } from "axios";
|
import axios, { Canceler } from "axios";
|
||||||
import { orderBy } from "lodash";
|
import { orderBy } from "lodash";
|
||||||
import React, { HTMLAttributes, useContext } from "react";
|
import React, { FocusEvent, HTMLAttributes, useContext } from "react";
|
||||||
import { DispatchProp } from "react-redux";
|
import { DispatchProp } from "react-redux";
|
||||||
|
|
||||||
import { LoadingPlaceholder, Select } from "@dndbeyond/character-components/es";
|
import { LoadingPlaceholder, Select } from "@dndbeyond/character-components/es";
|
||||||
@ -52,12 +52,12 @@ import { HtmlContent } from "~/components/HtmlContent";
|
|||||||
import { Link } from "~/components/Link";
|
import { Link } from "~/components/Link";
|
||||||
import { useSource } from "~/hooks/useSource";
|
import { useSource } from "~/hooks/useSource";
|
||||||
import { EditorWithDialog } from "~/subApps/builder/components/EditorWithDialog";
|
import { EditorWithDialog } from "~/subApps/builder/components/EditorWithDialog";
|
||||||
|
import { InputField } from "~/subApps/builder/components/InputField";
|
||||||
import { RouteKey } from "~/subApps/builder/constants";
|
import { RouteKey } from "~/subApps/builder/constants";
|
||||||
import {
|
import {
|
||||||
ModalData,
|
ModalData,
|
||||||
useModalManager,
|
useModalManager,
|
||||||
} from "~/subApps/builder/contexts/ModalManager";
|
} from "~/subApps/builder/contexts/ModalManager";
|
||||||
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
|
|
||||||
import { DetailChoice } from "~/tools/js/Shared/containers/DetailChoice";
|
import { DetailChoice } from "~/tools/js/Shared/containers/DetailChoice";
|
||||||
|
|
||||||
import { toastMessageActions } from "../../../../Shared/actions";
|
import { toastMessageActions } from "../../../../Shared/actions";
|
||||||
@ -195,7 +195,7 @@ class CustomBackgroundManager extends React.PureComponent<
|
|||||||
<div className="custom-background-property-value">
|
<div className="custom-background-property-value">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={name ? name : ""}
|
defaultValue={name || ""}
|
||||||
onBlur={this.handleTextInputBlur.bind(this, "name")}
|
onBlur={this.handleTextInputBlur.bind(this, "name")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -204,7 +204,7 @@ class CustomBackgroundManager extends React.PureComponent<
|
|||||||
<div className="custom-background-property custom-background-property-textarea">
|
<div className="custom-background-property custom-background-property-textarea">
|
||||||
<div className="custom-background-property-value">
|
<div className="custom-background-property-value">
|
||||||
<textarea
|
<textarea
|
||||||
defaultValue={description ? description : ""}
|
defaultValue={description || ""}
|
||||||
onBlur={this.handleTextInputBlur.bind(this, "description")}
|
onBlur={this.handleTextInputBlur.bind(this, "description")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -471,35 +471,35 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
dispatch(characterActions.traitSet(traitType, content));
|
dispatch(characterActions.traitSet(traitType, content));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHairChange = (value: string): void => {
|
handleHairChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.hairSet(value));
|
dispatch(characterActions.hairSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSkinChange = (value: string): void => {
|
handleSkinChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.skinSet(value));
|
dispatch(characterActions.skinSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEyesChange = (value: string): void => {
|
handleEyesChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.eyesSet(value));
|
dispatch(characterActions.eyesSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHeightChange = (value: string): void => {
|
handleHeightChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.heightSet(value));
|
dispatch(characterActions.heightSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleWeightChange = (value: string): void => {
|
handleWeightChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
let parsedValue = HelperUtils.parseInputInt(value);
|
let parsedValue = this.handleTransformValueToNumberOnBlur(e.target.value);
|
||||||
dispatch(characterActions.weightSet(parsedValue));
|
dispatch(characterActions.weightSet(parsedValue));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAgeChange = (value: string): void => {
|
handleAgeChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
let parsedValue = HelperUtils.parseInputInt(value);
|
let parsedValue = this.handleTransformValueToNumberOnBlur(e.target.value);
|
||||||
dispatch(characterActions.ageSet(parsedValue));
|
dispatch(characterActions.ageSet(parsedValue));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -517,9 +517,9 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
return clampedValue;
|
return clampedValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleGenderChange = (value: string): void => {
|
handleGenderChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.genderSet(value));
|
dispatch(characterActions.genderSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBackgroundChangePromise = (
|
handleBackgroundChangePromise = (
|
||||||
@ -608,9 +608,9 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
dispatch(characterActions.lifestyleSet(HelperUtils.parseInputInt(value)));
|
dispatch(characterActions.lifestyleSet(HelperUtils.parseInputInt(value)));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFaithChange = (value: string): void => {
|
handleFaithChange = (e: FocusEvent<HTMLInputElement>): void => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(characterActions.faithSet(value));
|
dispatch(characterActions.faithSet(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCustomBackgroundSave = (properties: any): void => {
|
handleCustomBackgroundSave = (properties: any): void => {
|
||||||
@ -716,7 +716,7 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
<DetailChoice
|
<DetailChoice
|
||||||
{...choice}
|
{...choice}
|
||||||
choice={choice}
|
choice={choice}
|
||||||
key={choiceId ? choiceId : ""}
|
key={choiceId || ""}
|
||||||
options={availableOptions}
|
options={availableOptions}
|
||||||
onChange={this.handleChoiceChange}
|
onChange={this.handleChoiceChange}
|
||||||
choiceInfo={choiceInfo}
|
choiceInfo={choiceInfo}
|
||||||
@ -1035,7 +1035,7 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
) : (
|
) : (
|
||||||
<HtmlContent
|
<HtmlContent
|
||||||
className="description-manage-background-description"
|
className="description-manage-background-description"
|
||||||
html={shortDescription ? shortDescription : ""}
|
html={shortDescription || ""}
|
||||||
withoutTooltips
|
withoutTooltips
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -1325,17 +1325,17 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
{alignmentDescriptionNode}
|
{alignmentDescriptionNode}
|
||||||
</div>
|
</div>
|
||||||
<div className="description-manage-faith">
|
<div className="description-manage-faith">
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Faith"
|
label="Faith"
|
||||||
initialValue={faith}
|
initialValue={faith}
|
||||||
onBlur={this.handleFaithChange}
|
onBlur={this.handleFaithChange}
|
||||||
inputAttributes={
|
maxLength={512}
|
||||||
|
inputProps={
|
||||||
{
|
{
|
||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
autoComplete: "off",
|
autoComplete: "off",
|
||||||
} as HTMLAttributes<HTMLInputElement>
|
} as HTMLAttributes<HTMLInputElement>
|
||||||
}
|
}
|
||||||
maxLength={512}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="description-manage-lifestyle">
|
<div className="description-manage-lifestyle">
|
||||||
@ -1367,47 +1367,52 @@ class DescriptionManage extends React.PureComponent<Props, State> {
|
|||||||
]}
|
]}
|
||||||
variant="paper"
|
variant="paper"
|
||||||
>
|
>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Hair"
|
label="Hair"
|
||||||
initialValue={hair}
|
initialValue={hair}
|
||||||
onBlur={this.handleHairChange}
|
onBlur={this.handleHairChange}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Skin"
|
label="Skin"
|
||||||
initialValue={skin}
|
initialValue={skin}
|
||||||
onBlur={this.handleSkinChange}
|
onBlur={this.handleSkinChange}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Eyes"
|
label="Eyes"
|
||||||
initialValue={eyes}
|
initialValue={eyes}
|
||||||
onBlur={this.handleEyesChange}
|
onBlur={this.handleEyesChange}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Height"
|
label="Height"
|
||||||
initialValue={height}
|
initialValue={height}
|
||||||
onBlur={this.handleHeightChange}
|
onBlur={this.handleHeightChange}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Weight (lbs)"
|
label="Weight (lbs)"
|
||||||
type={"number"}
|
type="number"
|
||||||
initialValue={weight}
|
initialValue={weight}
|
||||||
onBlur={this.handleWeightChange}
|
onBlur={this.handleWeightChange}
|
||||||
transformValueOnBlur={this.handleTransformValueToNumberOnBlur}
|
inputProps={numberInputAttributes}
|
||||||
inputAttributes={numberInputAttributes}
|
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Age (Years)"
|
label="Age (Years)"
|
||||||
type={"number"}
|
type="number"
|
||||||
initialValue={age}
|
initialValue={age}
|
||||||
onBlur={this.handleAgeChange}
|
onBlur={this.handleAgeChange}
|
||||||
transformValueOnBlur={this.handleTransformValueToNumberOnBlur}
|
inputProps={numberInputAttributes}
|
||||||
inputAttributes={numberInputAttributes}
|
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
|
className={styles.inputField}
|
||||||
label="Gender"
|
label="Gender"
|
||||||
initialValue={gender}
|
initialValue={gender}
|
||||||
onBlur={this.handleGenderChange}
|
onBlur={this.handleGenderChange}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
import React, { ChangeEvent, HTMLAttributes, ReactNode } from "react";
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
FocusEvent,
|
||||||
|
HTMLAttributes,
|
||||||
|
ReactNode,
|
||||||
|
} from "react";
|
||||||
import { DispatchProp } from "react-redux";
|
import { DispatchProp } from "react-redux";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -24,12 +29,12 @@ import {
|
|||||||
import { Dice } from "@dndbeyond/dice";
|
import { Dice } from "@dndbeyond/dice";
|
||||||
|
|
||||||
import { SourceCategoryDescription } from "~/constants";
|
import { SourceCategoryDescription } from "~/constants";
|
||||||
|
import { InputField } from "~/subApps/builder/components/InputField";
|
||||||
import { RouteKey } from "~/subApps/builder/constants";
|
import { RouteKey } from "~/subApps/builder/constants";
|
||||||
import {
|
import {
|
||||||
ModalData,
|
ModalData,
|
||||||
useModalManager,
|
useModalManager,
|
||||||
} from "~/subApps/builder/contexts/ModalManager";
|
} from "~/subApps/builder/contexts/ModalManager";
|
||||||
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
|
|
||||||
import UserRoles from "~/tools/js/Shared/constants/UserRoles";
|
import UserRoles from "~/tools/js/Shared/constants/UserRoles";
|
||||||
import PrivacyTypeRadio from "~/tools/js/smartComponents/PrivacyTypeRadio";
|
import PrivacyTypeRadio from "~/tools/js/smartComponents/PrivacyTypeRadio";
|
||||||
|
|
||||||
@ -723,66 +728,67 @@ class HomeBasicInfo extends React.PureComponent<Props> {
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Long Description"
|
label="Long Description"
|
||||||
initialValue={premadeInfo.definition.longDescription}
|
initialValue={premadeInfo.definition.longDescription}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.longDescription = value;
|
premadeInfo.definition.longDescription = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Short Description"
|
label="Short Description"
|
||||||
initialValue={premadeInfo.definition.shortDescription}
|
initialValue={premadeInfo.definition.shortDescription}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.shortDescription = value;
|
premadeInfo.definition.shortDescription = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Image Url"
|
label="Image Url"
|
||||||
initialValue={premadeInfo.definition.imageUrl}
|
initialValue={premadeInfo.definition.imageUrl}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.imageUrl = value;
|
premadeInfo.definition.imageUrl = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Image Alt Text"
|
label="Image Alt Text"
|
||||||
initialValue={premadeInfo.definition.imageAltText}
|
initialValue={premadeInfo.definition.imageAltText}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.imageAltText = value;
|
premadeInfo.definition.imageAltText = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Mobile Image Url"
|
label="Mobile Image Url"
|
||||||
initialValue={premadeInfo.definition.mobileImageUrl}
|
initialValue={premadeInfo.definition.mobileImageUrl}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.mobileImageUrl = value;
|
premadeInfo.definition.mobileImageUrl = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Mobile Image Accessibility"
|
label="Mobile Image Accessibility"
|
||||||
initialValue={premadeInfo.definition.mobileImageAccessibility}
|
initialValue={premadeInfo.definition.mobileImageAccessibility}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.mobileImageAccessibility = value;
|
premadeInfo.definition.mobileImageAccessibility =
|
||||||
|
e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormInputField
|
<InputField
|
||||||
label="Theme Color Hex Code"
|
label="Theme Color Hex Code"
|
||||||
initialValue={premadeInfo.definition.themeColor}
|
initialValue={premadeInfo.definition.themeColor}
|
||||||
inputAttributes={inputAttributes}
|
inputProps={inputAttributes}
|
||||||
onBlur={(value: string) => {
|
onBlur={(e: FocusEvent<HTMLInputElement>) => {
|
||||||
premadeInfo.definition.themeColor = value;
|
premadeInfo.definition.themeColor = e.target.value;
|
||||||
this.handlePremadeInfoChanged(premadeInfo);
|
this.handlePremadeInfoChanged(premadeInfo);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -62,8 +62,6 @@ interface Props {
|
|||||||
theme?: CharacterTheme;
|
theme?: CharacterTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoItemProps = { role: "listItem", inline: true };
|
|
||||||
|
|
||||||
export const ActionDetail = ({
|
export const ActionDetail = ({
|
||||||
showCustomize = true,
|
showCustomize = true,
|
||||||
largePoolMinAmount = 11,
|
largePoolMinAmount = 11,
|
||||||
@ -79,6 +77,8 @@ export const ActionDetail = ({
|
|||||||
theme,
|
theme,
|
||||||
inventoryLookup,
|
inventoryLookup,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const infoItemProps = { role: "listItem", inline: true };
|
||||||
|
|
||||||
const handleRemoveCustomizations = () => {
|
const handleRemoveCustomizations = () => {
|
||||||
if (onCustomizationsRemove) {
|
if (onCustomizationsRemove) {
|
||||||
onCustomizationsRemove();
|
onCustomizationsRemove();
|
||||||
@ -427,17 +427,17 @@ export const ActionDetail = ({
|
|||||||
</InfoItem>
|
</InfoItem>
|
||||||
)}
|
)}
|
||||||
{attackSubtypeId && (
|
{attackSubtypeId && (
|
||||||
<InfoItem label="Attack Type:">
|
<InfoItem label="Attack Type:" {...infoItemProps}>
|
||||||
{ActionUtils.getAttackSubtypeName(action)}
|
{ActionUtils.getAttackSubtypeName(action)}
|
||||||
</InfoItem>
|
</InfoItem>
|
||||||
)}
|
)}
|
||||||
{requiresAttackRoll && (
|
{requiresAttackRoll && (
|
||||||
<InfoItem label="To Hit:">
|
<InfoItem label="To Hit:" {...infoItemProps}>
|
||||||
<NumberDisplay type="signed" number={toHit} />
|
<NumberDisplay type="signed" number={toHit} />
|
||||||
</InfoItem>
|
</InfoItem>
|
||||||
)}
|
)}
|
||||||
{requiresSavingThrow && (
|
{requiresSavingThrow && (
|
||||||
<InfoItem label="Attack/Save:">
|
<InfoItem label="Attack/Save:" {...infoItemProps}>
|
||||||
{saveStateId !== null
|
{saveStateId !== null
|
||||||
? RuleDataUtils.getStatNameById(saveStateId, ruleData)
|
? RuleDataUtils.getStatNameById(saveStateId, ruleData)
|
||||||
: ""}{" "}
|
: ""}{" "}
|
||||||
@ -446,14 +446,14 @@ export const ActionDetail = ({
|
|||||||
)}
|
)}
|
||||||
{renderDamageProperties()}
|
{renderDamageProperties()}
|
||||||
{requiresAttackRoll && (
|
{requiresAttackRoll && (
|
||||||
<InfoItem label="Stat:">
|
<InfoItem label="Stat:" {...infoItemProps}>
|
||||||
{statId === null
|
{statId === null
|
||||||
? "--"
|
? "--"
|
||||||
: RuleDataUtils.getStatNameById(statId, ruleData)}
|
: RuleDataUtils.getStatNameById(statId, ruleData)}
|
||||||
</InfoItem>
|
</InfoItem>
|
||||||
)}
|
)}
|
||||||
{rangeAreas.length > 0 && (
|
{rangeAreas.length > 0 && (
|
||||||
<InfoItem label="Range/Area:">
|
<InfoItem label="Range/Area:" {...infoItemProps}>
|
||||||
{rangeAreas.map((node, idx) => (
|
{rangeAreas.map((node, idx) => (
|
||||||
<React.Fragment key={idx}>
|
<React.Fragment key={idx}>
|
||||||
{node}
|
{node}
|
||||||
@ -462,8 +462,16 @@ export const ActionDetail = ({
|
|||||||
))}
|
))}
|
||||||
</InfoItem>
|
</InfoItem>
|
||||||
)}
|
)}
|
||||||
{isProficient && <InfoItem label="Proficient:">Yes</InfoItem>}
|
{isProficient && (
|
||||||
{notes && <InfoItem label="Notes:">{notes}</InfoItem>}
|
<InfoItem label="Proficient:" {...infoItemProps}>
|
||||||
|
Yes
|
||||||
|
</InfoItem>
|
||||||
|
)}
|
||||||
|
{notes && (
|
||||||
|
<InfoItem label="Notes:" {...infoItemProps}>
|
||||||
|
{notes}
|
||||||
|
</InfoItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -92,7 +92,7 @@ export default function SpellManagerContainer({
|
|||||||
|
|
||||||
const getData = useCallback(
|
const getData = useCallback(
|
||||||
async function getData() {
|
async function getData() {
|
||||||
let classSpellMap = await spellsManager.getSpellShoppe();
|
let classSpellMap = await spellsManager.getSpellShoppe(true);
|
||||||
if (!classSpellMap[charClassId] || shouldFetch) {
|
if (!classSpellMap[charClassId] || shouldFetch) {
|
||||||
classSpellMap = await spellsManager.getSpellShoppe(true);
|
classSpellMap = await spellsManager.getSpellShoppe(true);
|
||||||
}
|
}
|
||||||
|
@ -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<HTMLDivElement>,
|
|
||||||
"onChange" | "onBlur" | "onFocus"
|
|
||||||
> {
|
|
||||||
label: string;
|
|
||||||
type?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
initialValue?: string | number | null;
|
|
||||||
inputAttributes?: HTMLAttributes<HTMLInputElement>;
|
|
||||||
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<FormInputFieldProps> = ({
|
|
||||||
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<HTMLInputElement>): 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<HTMLInputElement>) => {
|
|
||||||
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<HTMLInputElement>) => {
|
|
||||||
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 (
|
|
||||||
<div className={clsx(["builder-field", "form-input-field"])}>
|
|
||||||
<span className="builder-field-label">
|
|
||||||
<label
|
|
||||||
className="builder-field-heading form-input-field-label"
|
|
||||||
htmlFor={guid}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
<span className="builder-field-input">
|
|
||||||
<input
|
|
||||||
className="builder-field-value"
|
|
||||||
id={guid}
|
|
||||||
type={type}
|
|
||||||
placeholder={placeholder}
|
|
||||||
onChange={handleOnChange}
|
|
||||||
onBlur={handleOnBlur}
|
|
||||||
onFocus={handleOnFocus}
|
|
||||||
value={value ?? ""}
|
|
||||||
maxLength={maxLength || undefined}
|
|
||||||
{...inputAttributes}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<MaxLengthErrorMessage />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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<Array<PrerequisiteFailure>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className={clsx(["ddbc-prerequisite-failure-summary", className])}>
|
|
||||||
Missing: {strings.length > 0 ? strings : "Unknown"}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
import PrerequisiteFailureSummary from "./PrerequisiteFailureSummary";
|
|
||||||
|
|
||||||
export default PrerequisiteFailureSummary;
|
|
||||||
export { PrerequisiteFailureSummary };
|
|
@ -1,2 +1,2 @@
|
|||||||
// extracted by mini-css-extract-plugin
|
// 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"};
|
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"};
|
@ -0,0 +1,2 @@
|
|||||||
|
// extracted by mini-css-extract-plugin
|
||||||
|
export default {"label":"styles_label__FtMSp","input":"styles_input__2OhV7","error":"styles_error__kxBCf"};
|
@ -0,0 +1,2 @@
|
|||||||
|
// extracted by mini-css-extract-plugin
|
||||||
|
export default {"input":"styles_input__YM9-A"};
|
@ -1,2 +1,2 @@
|
|||||||
// extracted by mini-css-extract-plugin
|
// extracted by mini-css-extract-plugin
|
||||||
export default {"accordionGroup":"styles_accordionGroup__ijLDx"};
|
export default {"accordionGroup":"styles_accordionGroup__ijLDx","inputField":"styles_inputField__WePfs"};
|
Loading…
x
Reference in New Issue
Block a user