Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
AbilityManager,
|
||||
FeaturesManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { AttributesManagerContext } from "~/tools/js/Shared/managers/AttributesManagerContext";
|
||||
|
||||
export function useAbilities() {
|
||||
const { attributesManager } = useContext(AttributesManagerContext);
|
||||
|
||||
const [abilities, setAbilities] = useState<Array<AbilityManager>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function onUpdate() {
|
||||
const abilities = await attributesManager.getAbilities();
|
||||
|
||||
setAbilities(abilities);
|
||||
}
|
||||
return FeaturesManager.subscribeToUpdates({ onUpdate });
|
||||
}, [attributesManager, setAbilities]);
|
||||
|
||||
return abilities;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
const identity = (x) => x;
|
||||
|
||||
/**
|
||||
* Hook that encapsulates loading/error state setting when calling an API promise
|
||||
* Usage:
|
||||
* const [
|
||||
* doThing, // callback to invoke to initiate the API call
|
||||
* isDoingThing, // boolean indicating if the API call is in progress
|
||||
* errorDoingThing, // error object indicating if there was an error caught (whatever param is passed to the .catch() callback)
|
||||
* ] = useApiCall(MyApiUtils.doThing);
|
||||
* @param {function} apiFunc The API function to use for setting loading/error state - Must return a promise!
|
||||
* @param {function} [onSuccess] Optional callback invoked if the apiFunc promise succeeds
|
||||
* @returns {[function, boolean, any]} An array containing a callback to initiate the API call, a isLoading boolean, and an error object
|
||||
*/
|
||||
const useApiCall = (apiFunc, onSuccess = identity) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const makeCall = useCallback(
|
||||
(...args) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
apiFunc(...args)
|
||||
.then(onSuccess)
|
||||
.then(() => {
|
||||
setIsLoading(false);
|
||||
})
|
||||
|
||||
.catch((err) => {
|
||||
setIsLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
},
|
||||
[apiFunc, onSuccess]
|
||||
);
|
||||
|
||||
return [
|
||||
makeCall as (...args: any[]) => void,
|
||||
isLoading as boolean,
|
||||
error as any,
|
||||
];
|
||||
};
|
||||
|
||||
export default useApiCall;
|
||||
@@ -0,0 +1,428 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
AbilityUtils as abilityUtils,
|
||||
AccessUtils as accessUtils,
|
||||
ActionUtils as actionUtils,
|
||||
ActivationUtils as activationUtils,
|
||||
ApiAdapterUtils as apiAdapterUtils,
|
||||
BackgroundUtils as backgroundUtils,
|
||||
CampaignUtils as campaignUtils,
|
||||
CampaignSettingUtils as campaignSettingUtils,
|
||||
CharacterUtils as characterUtils,
|
||||
ChoiceUtils as choiceUtils,
|
||||
ClassFeatureUtils as classFeatureUtils,
|
||||
ClassUtils as classUtils,
|
||||
ConditionUtils as conditionUtils,
|
||||
ConditionLevelUtils as conditionLevelUtils,
|
||||
ContainerUtils as containerUtils,
|
||||
CoreUtils as coreUtils,
|
||||
CreatureUtils as creatureUtils,
|
||||
CreatureRuleUtils as creatureRuleUtils,
|
||||
DataOriginUtils as dataOriginUtils,
|
||||
DecorationUtils as decorationUtils,
|
||||
DefinitionUtils as definitionUtils,
|
||||
DefinitionPoolUtils as definitionPoolUtils,
|
||||
DiceUtils as diceUtils,
|
||||
DurationUtils as durationUtils,
|
||||
EntityUtils as entityUtils,
|
||||
ExtraUtils as extraUtils,
|
||||
FeatUtils as featUtils,
|
||||
FeatureFlagInfoUtils as featureFlagInfoUtils,
|
||||
FeatureUtils as featureUtils,
|
||||
FormatUtils as formatUtils,
|
||||
HelperUtils as helperUtils,
|
||||
InfusionChoiceUtils as infusionChoiceUtils,
|
||||
InfusionUtils as infusionUtils,
|
||||
ItemUtils as itemUtils,
|
||||
KnownInfusionUtils as knownInfusionUtils,
|
||||
LimitedUseUtils as limitedUseUtils,
|
||||
ModifierUtils as modifierUtils,
|
||||
NoteUtils as noteUtils,
|
||||
OptionalClassFeatureUtils as optionalClassFeatureUtils,
|
||||
OptionalOriginUtils as optionalOriginUtils,
|
||||
OptionUtils as optionUtils,
|
||||
OrganizationUtils as organizationUtils,
|
||||
PdfUtils as pdfUtils,
|
||||
PrerequisiteUtils as prerequisiteUtils,
|
||||
RaceUtils as raceUtils,
|
||||
RacialTraitUtils as racialTraitUtils,
|
||||
RuleDataUtils as ruleDataUtils,
|
||||
RuleDataPoolUtils as ruleDataPoolUtils,
|
||||
SkillUtils as skillUtils,
|
||||
SnippetUtils as snippetUtils,
|
||||
SourceUtils as sourceUtils,
|
||||
SpellUtils as spellUtils,
|
||||
StartingEquipmentUtils as startingEquipmentUtils,
|
||||
ValueUtils as valueUtils,
|
||||
VehicleUtils as vehicleUtils,
|
||||
VehicleComponentUtils as vehicleComponentUtils,
|
||||
apiCreatorSelectors as api,
|
||||
rulesEngineSelectors as s,
|
||||
FeatList,
|
||||
characterSelectors as char,
|
||||
characterActions,
|
||||
serviceDataSelectors as sds,
|
||||
serviceDataActions,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
/**
|
||||
* A hook that provides access to the character rules engine state without
|
||||
* having to import rules engine all over the place in your components. This
|
||||
* reduces touchpoints to the rules engine and makes it easier to refactor the
|
||||
* rules engine in the future.
|
||||
*/
|
||||
export const useCharacterEngine = () => ({
|
||||
// Character Actions
|
||||
characterActions,
|
||||
serviceDataActions,
|
||||
// Character Selectors that aren't passed thru to Rules Engine Selectors
|
||||
characterStatusSlug: useSelector(char.getStatusSlug),
|
||||
// Rules Engine Selector Values
|
||||
abilities: useSelector(s.getAbilities),
|
||||
abilityLookup: useSelector(s.getAbilityLookup),
|
||||
abilityKey: useSelector(s.getAbilityKeyLookup),
|
||||
actionLookup: useSelector(s.getActionLookup),
|
||||
acAdjustments: useSelector(s.getAcAdjustments),
|
||||
acSuppliers: useSelector(s.getArmorClassSuppliers),
|
||||
acTotal: useSelector(s.getAcTotal),
|
||||
actions: useSelector(s.getActions),
|
||||
activatables: useSelector(s.getActivatables),
|
||||
activeCharacterSpells: useSelector(s.getActiveCharacterSpells),
|
||||
activeClassSpells: useSelector(s.getActiveClassSpells),
|
||||
activeConditions: useSelector(s.getActiveConditions),
|
||||
activeGroupedImmunities: useSelector(s.getActiveGroupedImmunities),
|
||||
activeGroupedResistances: useSelector(s.getActiveGroupedResistances),
|
||||
activeGroupedVulnerabilities: useSelector(s.getActiveGroupedVulnerabilities),
|
||||
activeImmunities: useSelector(s.getActiveImmunities),
|
||||
activeResistances: useSelector(s.getActiveResistances),
|
||||
activeVulnerabilities: useSelector(s.getActiveVulnerabilities),
|
||||
activeSource: useSelector(s.getActiveSourceLookup),
|
||||
activeSourceCategories: useSelector(s.getActiveSourceCategories),
|
||||
activeSources: useSelector(s.getActiveSources),
|
||||
activeSpellAttackList: useSelector(s.getActiveSpellAttackList),
|
||||
advantageSavingThrowModifiers: useSelector(
|
||||
s.getAdvantageSavingThrowModifiers
|
||||
),
|
||||
alignment: useSelector(s.getAlignment),
|
||||
allInventoryItems: useSelector(s.getAllInventoryItems),
|
||||
armorSpeedAdjustmentAmount: useSelector(s.getArmorSpeedAdjustmentAmount),
|
||||
attacks: useSelector(s.getAttacks),
|
||||
attacksPerActionInfo: useSelector(s.getAttacksPerActionInfo),
|
||||
attunedItems: useSelector(s.getAttunedItems),
|
||||
attunedItemCountMax: useSelector(s.getAttunedItemCountMax),
|
||||
attunedSlots: useSelector(s.getAttunedSlots),
|
||||
availableInfusionChoices: useSelector(s.getAvailableInfusionChoices),
|
||||
availablePactMagicSlotLevels: useSelector(s.getAvailablePactMagicSlotLevels),
|
||||
availableSpellSlotLevels: useSelector(s.getAvailableSpellSlotLevels),
|
||||
backgroundChoice: useSelector(s.getBackgroundChoiceLookup),
|
||||
backgroundInfo: useSelector(s.getBackgroundInfo),
|
||||
backgroundModifier: useSelector(s.getBackgroundModifierLookup),
|
||||
backgroundModifiers: useSelector(s.getBackgroundModifiers),
|
||||
baseClass: useSelector(s.getBaseClassLookup),
|
||||
baseFeat: useSelector(s.getBaseFeatLookup),
|
||||
baseFeats: useSelector(s.getBaseFeats),
|
||||
bonusSavingThrowModifiers: useSelector(s.getBonusSavingThrowModifiers),
|
||||
campaign: useSelector(s.getCampaign),
|
||||
campaignSettings: useSelector(sds.getCampaignSettings),
|
||||
carryingCapacity: useSelector(s.getCarryCapacity),
|
||||
castableSpellSlotLevels: useSelector(s.getCastableSpellSlotLevels),
|
||||
castablePactMagicSlotLevels: useSelector(s.getCastablePactMagicSlotLevels),
|
||||
characterConfig: useSelector(s.getCharacterConfiguration),
|
||||
characterGender: useSelector(s.getGender),
|
||||
characterId: useSelector(s.getId),
|
||||
characterInventoryContainers: useSelector(s.getCharacterInventoryContainers),
|
||||
characterName: useSelector(s.getName),
|
||||
characterNotes: useSelector(s.getCharacterNotes),
|
||||
characterSpells: useSelector(s.getCharacterSpells),
|
||||
characterTheme: useSelector(s.getCharacterTheme),
|
||||
characterTraits: useSelector(s.getCharacterTraits),
|
||||
choiceInfo: useSelector(s.getChoiceInfo),
|
||||
classAction: useSelector(s.getClassActionLookup),
|
||||
classAlwaysKnownSpells: useSelector(sds.getClassAlwaysKnownSpells),
|
||||
classAlwaysPreparedSpells: useSelector(sds.getClassAlwaysPreparedSpells),
|
||||
classChoice: useSelector(s.getClassChoiceLookup),
|
||||
classCreatureRules: useSelector(s.getClassCreatureRules),
|
||||
classFeature: useSelector(s.getClassFeatureLookup),
|
||||
classMappingId: useSelector(s.getClassMappingIdLookupByActiveId),
|
||||
classModifier: useSelector(s.getClassModifierLookup),
|
||||
classOption: useSelector(s.getClassOptionLookup),
|
||||
classSpell: useSelector(s.getClassSpellLookup),
|
||||
classSpells: useSelector(s.getClassSpells),
|
||||
classSpellInfo: useSelector(s.getClassSpellInfoLookup),
|
||||
classSpellLists: useSelector(s.getClassSpellLists),
|
||||
classSpellListSpells: useSelector(s.getClassSpellListSpellsLookup),
|
||||
classes: useSelector(s.getClasses),
|
||||
classesModifiers: useSelector(s.getClassesModifiers),
|
||||
combinedMaxSpellSlotLevel: useSelector(s.getCombinedMaxSpellSlotLevel),
|
||||
combinedSpellSlots: useSelector(s.getCombinedSpellSlots),
|
||||
conModifier: useSelector(s.getConModifier),
|
||||
conditionImmunityData: useSelector(s.getConditionImmunityData),
|
||||
conditionModifier: useSelector(s.getConditionModifierLookup),
|
||||
conditionModifiers: useSelector(s.getConditionModifiers),
|
||||
container: useSelector(s.getContainerLookup),
|
||||
containerCoinWeight: useSelector(s.getCointainerCoinWeight),
|
||||
containerItemWeight: useSelector(s.getContainerItemWeight),
|
||||
containerLookup: useSelector(s.getContainerLookup),
|
||||
creature: useSelector(s.getCreatureLookup),
|
||||
creatureGroupRules: useSelector(s.getCreatureGroupRulesLookup),
|
||||
creatureInfusion: useSelector(s.getCreatureInfusionLookup),
|
||||
creatureOwnerData: useSelector(s.getCreatureOwnerData),
|
||||
creatureRules: useSelector(s.getCreatureRules),
|
||||
creatures: useSelector(s.getCreatures),
|
||||
currentCarriedWeightSpeed: useSelector(s.getCurrentCarriedWeightSpeed),
|
||||
currentCarriedWeightType: useSelector(s.getCurrentCarriedWeightType),
|
||||
currentLevel: useSelector(s.getCurrentLevel),
|
||||
currentXp: useSelector(s.getCurrentXp),
|
||||
currentLevelRacialTraits: useSelector(s.getCurrentLevelRacialTraits),
|
||||
customActions: useSelector(s.getCustomActions),
|
||||
customConditionAdjustments: useSelector(s.getCustomConditionAdjustments),
|
||||
customDamageAdjustments: useSelector(s.getCustomDamageAdjustments),
|
||||
customItems: useSelector(s.getCustomItems),
|
||||
customImmunityDamageAdjustments: useSelector(
|
||||
s.getCustomImmunityDamageAdjustments
|
||||
),
|
||||
customResistanceDamageAdjustments: useSelector(
|
||||
s.getCustomResistanceDamageAdjustments
|
||||
),
|
||||
customVulnerabilityDamageAdjustments: useSelector(
|
||||
s.getCustomVulnerabilityDamageAdjustments
|
||||
),
|
||||
customSkills: useSelector(s.getCustomSkills),
|
||||
customSkillProficiencies: useSelector(s.getCustomSkillProficiencies),
|
||||
damageImmunityData: useSelector(s.getDamageImmunityData),
|
||||
deathSaveInfo: useSelector(s.getDeathSaveInfo),
|
||||
deathCause: useSelector(s.getDeathCause),
|
||||
decorationInfo: useSelector(s.getDecorationInfo),
|
||||
dedicatedWeaponEnabled: useSelector(s.getDedicatedWeaponEnabled),
|
||||
definitionPool: useSelector(sds.getDefinitionPool),
|
||||
dexModifier: useSelector(s.getDexModifier),
|
||||
disadvantageSavingThrowModifiers: useSelector(
|
||||
s.getDisadvantageSavingThrowModifiers
|
||||
),
|
||||
encumberedWeight: useSelector(s.getEncumberedWeight),
|
||||
entityRestrictionData: useSelector(s.getEntityRestrictionData),
|
||||
entityValueLookup: useSelector(s.getCharacterValueLookupByEntity),
|
||||
equippedItems: useSelector(s.getEquippedItems),
|
||||
experienceInfo: useSelector(s.getExperienceInfo),
|
||||
expertiseModifiers: useSelector(s.getExpertiseModifiers),
|
||||
extras: useSelector(s.getExtras),
|
||||
featLookup: useSelector(s.getFeatLookup),
|
||||
feats: useSelector(s.getFeats),
|
||||
featAction: useSelector(s.getFeatActionLookup),
|
||||
featCreatureRules: useSelector(s.getFeatCreatureRules),
|
||||
featModifier: useSelector(s.getFeatModifierLookup),
|
||||
featModifiers: useSelector(s.getFeatModifiers),
|
||||
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),
|
||||
heavilyEncumberedWeight: useSelector(s.getHeavilyEncumberedWeight),
|
||||
hexWeaponEnabled: useSelector(s.getHexWeaponEnabled),
|
||||
highestEquippedArmorAC: useSelector(s.getHighestAcEquippedArmor),
|
||||
highestEquippedShieldAC: useSelector(s.getHighestAcEquippedShield),
|
||||
hpInfo: useSelector(s.getHitPointInfo),
|
||||
improvedPactWeaponEnabled: useSelector(s.getImprovedPactWeaponEnabled),
|
||||
infusions: useSelector(s.getInfusions),
|
||||
infusionChoices: useSelector(s.getInfusionChoices),
|
||||
infusionChoiceLookup: useSelector(s.getInfusionChoiceLookup),
|
||||
infusionChoiceInfusion: useSelector(s.getInfusionChoiceInfusionLookup),
|
||||
infusionMappings: useSelector(sds.getInfusionsMappings),
|
||||
innateNaturalActions: useSelector(s.getInnateNaturalActions),
|
||||
inventory: useSelector(s.getInventory),
|
||||
inventoryContainers: useSelector(s.getInventoryContainers),
|
||||
inventoryLookup: useSelector(s.getInventoryLookup),
|
||||
inventoryInfusion: useSelector(s.getInventoryInfusionLookup),
|
||||
isSheetReady: useSelector(s.isCharacterSheetReady),
|
||||
isDead: useSelector(s.isDead),
|
||||
isMulticlassCharacter: useSelector(s.isMulticlassCharacter),
|
||||
itemAttacks: useSelector(s.getItemAttacks),
|
||||
itemModifier: useSelector(s.getItemModifierLookup),
|
||||
itemSpell: useSelector(s.getItemSpellLookup),
|
||||
kenseiModifiers: useSelector(s.getKenseiModifiers),
|
||||
knownInfusion: useSelector(s.getKnownInfusionLookup),
|
||||
knownInfusionByChoice: useSelector(s.getKnownInfusionLookupByChoiceKey),
|
||||
knownInfusions: useSelector(s.getKnownInfusions),
|
||||
knownInfusionMappings: useSelector(sds.getKnownInfusionsMappings),
|
||||
knownReplicatedItems: useSelector(s.getKnownReplicatedItems),
|
||||
languages: useSelector(s.getLanguages),
|
||||
languageModifiers: useSelector(s.getLanguageModifiers),
|
||||
levelSpells: useSelector(s.getLevelSpells),
|
||||
lifestyle: useSelector(s.getLifestyle),
|
||||
loadSpecies: useSelector(api.makeLoadAvailableRaces),
|
||||
maxPactMagicSlotLevel: useSelector(s.getMaxPactMagicSlotLevel),
|
||||
maxSpellSlotLevel: useSelector(s.getMaxSpellSlotLevel),
|
||||
miscModifiers: useSelector(s.getMiscModifiers),
|
||||
modifierData: useSelector(s.getModifierData),
|
||||
optionalClassFeature: useSelector(s.getOptionalClassFeatureLookup),
|
||||
optionalClassFeatures: useSelector(s.getOptionalClassFeatures),
|
||||
optionalOrigin: useSelector(s.getOptionalOriginLookup),
|
||||
optionalOrigins: useSelector(s.getOptionalOrigins),
|
||||
originRef: useSelector(s.getDataOriginRefData),
|
||||
originRefBackground: useSelector(s.getDataOriginRefBackgroundData),
|
||||
originRefClass: useSelector(s.getDataOriginRefClassData),
|
||||
originRefCondition: useSelector(s.getDataOriginRefConditionData),
|
||||
originRefFeat: useSelector(s.getDataOriginRefFeatData),
|
||||
originRefFeatList: useSelector(s.getDataOriginRefFeatListData),
|
||||
originRefItem: useSelector(s.getDataOriginRefItemData),
|
||||
originRefRace: useSelector(s.getDataOriginRefRaceData),
|
||||
originRefVehicle: useSelector(s.getDataOriginRefVehicleData),
|
||||
overallSpellInfo: useSelector(s.getOverallSpellInfo),
|
||||
overridePassiveInsight: useSelector(s.getOverridePassiveInsight),
|
||||
overridePassiveInvestigation: useSelector(s.getOverridePassiveInvestigation),
|
||||
overridePassivePerception: useSelector(s.getOverridePassivePerception),
|
||||
pactMagicConditiones: useSelector(s.getPactMagicClasses),
|
||||
pactMagicSlots: useSelector(s.getPactMagicSlots),
|
||||
pactWeaponEnabled: useSelector(s.getPactWeaponEnabled),
|
||||
partyInfo: useSelector(sds.getPartyInfo),
|
||||
partyInventory: useSelector(s.getPartyInventory),
|
||||
partyInventoryContainers: useSelector(s.getPartyInventoryContainers),
|
||||
partyInventoryItem: useSelector(s.getPartyInventoryLookup),
|
||||
passiveInsight: useSelector(s.getPassiveInsight),
|
||||
passiveInvestigation: useSelector(s.getPassiveInvestigation),
|
||||
passivePerception: useSelector(s.getPassivePerception),
|
||||
pdfBucket1: useSelector(s.getPdfDataBucket1),
|
||||
pdfBucket2: useSelector(s.getPdfDataBucket2),
|
||||
pdfBucket3: useSelector(s.getPdfDataBucket3),
|
||||
pdfBucket4: useSelector(s.getPdfDataBucket4),
|
||||
pdfBucket5: useSelector(s.getPdfDataBucket5),
|
||||
pdfBucket6: useSelector(s.getPdfDataBucket6),
|
||||
pdfExportData: useSelector(s.getPdfExportData),
|
||||
playerId: useSelector(s.getUserId),
|
||||
playerName: useSelector(s.getUsername),
|
||||
preferences: useSelector(s.getCharacterPreferences),
|
||||
prerequisiteData: useSelector(s.getPrerequisiteData),
|
||||
processedInitiative: useSelector(s.getProcessedInitiative),
|
||||
proficiency: useSelector(s.getProficiencyLookup),
|
||||
proficiencyBonus: useSelector(s.getProficiencyBonus),
|
||||
proficiencyGroups: useSelector(s.getProficiencyGroups),
|
||||
proficiencyModifiers: useSelector(s.getProficiencyModifiers),
|
||||
protectionSuppliers: useSelector(s.getProtectionSuppliers),
|
||||
pushDragLiftWeight: useSelector(s.getPushDragLiftWeight),
|
||||
race: useSelector(s.getRace),
|
||||
raceAction: useSelector(s.getRaceActionLookup),
|
||||
raceChoice: useSelector(s.getRaceChoiceLookup),
|
||||
raceCreatureRules: useSelector(s.getRaceCreatureRules),
|
||||
raceModifier: useSelector(s.getRaceModifierLookup),
|
||||
raceModifiers: useSelector(s.getRaceModifiers),
|
||||
raceOption: useSelector(s.getRaceOptionLookup),
|
||||
raceSpell: useSelector(s.getRaceSpellLookup),
|
||||
requiredCharacterServiceParams: useSelector(
|
||||
s.getRequiredCharacterServiceParams
|
||||
),
|
||||
requiredGameDataServiceParams: useSelector(
|
||||
s.getRequiredGameDataServiceParams
|
||||
),
|
||||
resistanceData: useSelector(s.getResistanceData),
|
||||
restrictedBonusSavingThrowModifiers: useSelector(
|
||||
s.getRestrictedBonusSavingThrowModifiers
|
||||
),
|
||||
ritualSpells: useSelector(s.getRitualSpells),
|
||||
ruleData: useSelector(s.getRuleData),
|
||||
ruleDataPool: useSelector(sds.getRuleDataPool),
|
||||
savingThrowDiceAdjustments: useSelector(s.getSavingThrowDiceAdjustments),
|
||||
senseInfo: useSelector(s.getSenseInfo),
|
||||
situationalBonusSavingThrowsLookup: useSelector(
|
||||
s.getSituationalBonusSavingThrowsLookup
|
||||
),
|
||||
situationalBonusSavingThrows: useSelector(s.getSituationalBonusSavingThrows),
|
||||
size: useSelector(s.getSize),
|
||||
skill: useSelector(s.getSkillLookup),
|
||||
skills: useSelector(s.getSkills),
|
||||
snippetData: useSelector(s.getSnippetData),
|
||||
socialImageData: useSelector(s.getSocialImageData),
|
||||
specialWeaponPropertiesEnabled: useSelector(
|
||||
s.hack__getSpecialWeaponPropertiesEnabled
|
||||
),
|
||||
speeds: useSelector(s.getSpeeds),
|
||||
spellClasses: useSelector(s.getSpellClasses),
|
||||
spellcasterInfo: useSelector(s.getSpellCasterInfo),
|
||||
spellcastingClasses: useSelector(s.getSpellcastingClasses),
|
||||
spellListDataOrigin: useSelector(s.getSpellListDataOriginLookup),
|
||||
spellSlots: useSelector(s.getSpellSlots),
|
||||
startingClass: useSelector(s.getStartingClass),
|
||||
strScore: useSelector(s.getStrScore),
|
||||
totalCarriedWeight: useSelector(s.getTotalCarriedWeight),
|
||||
totalClassLevel: useSelector(s.getTotalClassLevel),
|
||||
uniqueProficiencyModifiers: useSelector(s.getUniqueProficiencyModifiers),
|
||||
validEquipmentModifiers: useSelector(s.getValidEquipmentModifiers),
|
||||
validGlobalModifiers: useSelector(s.getValidGlobalModifiers),
|
||||
validImmunityModifiers: useSelector(s.getValidImmunityModifiers),
|
||||
validResistanceModifiers: useSelector(s.getValidResistanceModifiers),
|
||||
validVulnerabilityModifiers: useSelector(s.getValidVulnerabilityModifiers),
|
||||
valueLookup: useSelector(s.getCharacterValueLookup),
|
||||
vehicle: useSelector(s.getVehicleLookup),
|
||||
vehicles: useSelector(s.getVehicles),
|
||||
vehicleComponent: useSelector(s.getVehicleComponentLookup),
|
||||
vehicleComponentMappings: useSelector(sds.getVehicleComponentMappings),
|
||||
vehicleMappings: useSelector(sds.getVehicleMappings),
|
||||
vulnerabilityData: useSelector(s.getVulnerabilityData),
|
||||
weaponSpellDamageGroups: useSelector(s.getWeaponSpellDamageGroups),
|
||||
weightSpeeds: useSelector(s.getWeightSpeeds),
|
||||
|
||||
// Utility Functions
|
||||
abilityUtils,
|
||||
accessUtils,
|
||||
actionUtils,
|
||||
activationUtils,
|
||||
apiAdapterUtils,
|
||||
backgroundUtils,
|
||||
campaignUtils,
|
||||
campaignSettingUtils,
|
||||
characterUtils,
|
||||
choiceUtils,
|
||||
classFeatureUtils,
|
||||
classUtils,
|
||||
conditionUtils,
|
||||
conditionLevelUtils,
|
||||
containerUtils,
|
||||
coreUtils,
|
||||
creatureUtils,
|
||||
creatureRuleUtils,
|
||||
dataOriginUtils,
|
||||
decorationUtils,
|
||||
definitionUtils,
|
||||
definitionPoolUtils,
|
||||
diceUtils,
|
||||
durationUtils,
|
||||
entityUtils,
|
||||
extraUtils,
|
||||
featUtils,
|
||||
featureFlagInfoUtils,
|
||||
featureUtils,
|
||||
formatUtils,
|
||||
helperUtils,
|
||||
infusionChoiceUtils,
|
||||
infusionUtils,
|
||||
itemUtils,
|
||||
knownInfusionUtils,
|
||||
limitedUseUtils,
|
||||
modifierUtils,
|
||||
noteUtils,
|
||||
optionalClassFeatureUtils,
|
||||
optionalOriginUtils,
|
||||
optionUtils,
|
||||
organizationUtils,
|
||||
pdfUtils,
|
||||
prerequisiteUtils,
|
||||
raceUtils,
|
||||
racialTraitUtils,
|
||||
ruleDataUtils,
|
||||
ruleDataPoolUtils,
|
||||
skillUtils,
|
||||
snippetUtils,
|
||||
sourceUtils,
|
||||
spellUtils,
|
||||
startingEquipmentUtils,
|
||||
valueUtils,
|
||||
vehicleUtils,
|
||||
vehicleComponentUtils,
|
||||
|
||||
// Misc
|
||||
FeatList,
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { characterSelectors } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { toastMessageActions } from "~/tools/js/Shared/actions";
|
||||
|
||||
import {
|
||||
claimCharacter as claimCharacterApi,
|
||||
joinCampaign as joinCampaignApi,
|
||||
} from "../helpers/characterServiceApi";
|
||||
|
||||
interface UseClaimCharacterProps {
|
||||
campaignJoinCode: string | null;
|
||||
isAssigned: boolean;
|
||||
}
|
||||
|
||||
export const useClaimCharacter = ({
|
||||
campaignJoinCode,
|
||||
isAssigned,
|
||||
}: UseClaimCharacterProps) => {
|
||||
const [isFinished, setIsFinished] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [newCharacterId, setNewCharacterId] = useState<number | null>(null);
|
||||
const [campaignId, setCampaignId] = useState<number | null>(null);
|
||||
|
||||
const characterId = useSelector(characterSelectors.getId);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const claimCharacter = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
let clonedId;
|
||||
|
||||
const claimResponse = await claimCharacterApi(characterId, isAssigned);
|
||||
if (claimResponse.ok) {
|
||||
const data = await claimResponse.json();
|
||||
clonedId = data.id;
|
||||
setNewCharacterId(clonedId);
|
||||
} else {
|
||||
throw new Error(`There was an error claiming character ${characterId}`);
|
||||
}
|
||||
|
||||
if (campaignJoinCode) {
|
||||
const joinResponse = await joinCampaignApi(campaignJoinCode, clonedId);
|
||||
if (joinResponse.ok) {
|
||||
const data = await joinResponse.json();
|
||||
setCampaignId(data.campaignId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`There was an error adding character ${clonedId} to campaign join code ${campaignJoinCode}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
dispatch(
|
||||
toastMessageActions.toastError("Error", "An unexpected error occurred.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsFinished(true);
|
||||
}
|
||||
}, [campaignJoinCode, characterId, dispatch, isAssigned]);
|
||||
|
||||
return [
|
||||
claimCharacter,
|
||||
isLoading,
|
||||
isFinished,
|
||||
newCharacterId,
|
||||
campaignId,
|
||||
] as const;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { HTMLAttributes, useState } from "react";
|
||||
import styles from '../../styles/errors.module.css';
|
||||
|
||||
export interface ErrorHandlerOptions {
|
||||
initialState: boolean;
|
||||
errMsg: string;
|
||||
errorMessageAttributes?: HTMLAttributes<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export interface ErrorHandler {
|
||||
showError: boolean;
|
||||
setShowError: (value: boolean) => void;
|
||||
ErrorMessage: () => JSX.Element;
|
||||
};
|
||||
|
||||
export const useErrorHandling = (
|
||||
initialState: boolean,
|
||||
errMsg: string,
|
||||
errorMessageAttributes: HTMLAttributes<HTMLDivElement> | null = null
|
||||
): ErrorHandler => {
|
||||
const [showError, setShowError] = useState(initialState);
|
||||
const ErrorMessage = (): JSX.Element => (
|
||||
<div className={styles.inputError} {...errorMessageAttributes}>
|
||||
{errMsg}
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
showError,
|
||||
setShowError,
|
||||
ErrorMessage,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { HTMLAttributes } from "react";
|
||||
import { useErrorHandling } from "./useErrorHandling";
|
||||
|
||||
export interface MaxLengthErrorHandler {
|
||||
handleMaxLengthErrorMsg: (value: string) => void;
|
||||
hideError: () => void;
|
||||
MaxLengthErrorMessage: () => JSX.Element;
|
||||
};
|
||||
|
||||
export const useMaxLengthErrorHandling = (
|
||||
initialState: boolean,
|
||||
maxLength: number | null,
|
||||
errMsg: string = "",
|
||||
errorMessageAttributes?: HTMLAttributes<HTMLDivElement>
|
||||
): MaxLengthErrorHandler => {
|
||||
errMsg ||= `The max length is ${maxLength} characters.`;
|
||||
|
||||
const {
|
||||
showError,
|
||||
setShowError,
|
||||
ErrorMessage,
|
||||
} = useErrorHandling(initialState, errMsg, errorMessageAttributes);
|
||||
|
||||
const handleMaxLengthErrorMsg = (value: string): void => {
|
||||
if (!maxLength) {
|
||||
// Skip if maxLength is 0 or not set.
|
||||
return;
|
||||
}
|
||||
|
||||
const isTooLong = (value?.length ?? 0) >= (maxLength ?? 0);
|
||||
if (isTooLong !== showError) {
|
||||
setShowError(isTooLong);
|
||||
}
|
||||
};
|
||||
|
||||
const hideError = () => {
|
||||
setShowError(false);
|
||||
}
|
||||
|
||||
const MaxLengthErrorMessage = (): JSX.Element => showError && errMsg
|
||||
? (<ErrorMessage />)
|
||||
: <></>;
|
||||
|
||||
return {
|
||||
handleMaxLengthErrorMsg,
|
||||
MaxLengthErrorMessage,
|
||||
hideError
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
ExtraManager,
|
||||
FeaturesManager,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { ExtrasManagerContext } from "~/tools/js/Shared/managers/ExtrasManagerContext";
|
||||
|
||||
//TODO: need to look at subscribeToUpdates for how many times its being called on state updates
|
||||
export function useExtras() {
|
||||
const { extrasManager } = useContext(ExtrasManagerContext);
|
||||
|
||||
const [extras, setExtras] = useState<Array<ExtraManager>>(
|
||||
extrasManager.getCharacterExtraManagers()
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onUpdate = () => {
|
||||
setExtras(extrasManager.getCharacterExtraManagers());
|
||||
};
|
||||
return FeaturesManager.subscribeToUpdates({ onUpdate });
|
||||
}, [extrasManager]);
|
||||
|
||||
return extras;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
|
||||
import { tryGet, tryRemove, trySet } from "../helpers/localStorageUtils";
|
||||
|
||||
//got most of the typing for this at https://usehooks-ts.com/react-hook/use-local-storage
|
||||
|
||||
type SetValue<T> = Dispatch<SetStateAction<T>>;
|
||||
type RemoveValue<T> = Dispatch<SetStateAction<T>>;
|
||||
function useLocalStorage<T>(
|
||||
key: string,
|
||||
defaultValue: T
|
||||
): [T, SetValue<T>, RemoveValue<T>] {
|
||||
const getValue = useCallback(() => {
|
||||
const savedValue = tryGet(key);
|
||||
|
||||
return savedValue === null
|
||||
? (defaultValue as T)
|
||||
: (parseJSON(savedValue) as T);
|
||||
}, [key, defaultValue]);
|
||||
|
||||
const [value, setValue] = useState(getValue());
|
||||
|
||||
useEffect(() => {
|
||||
setValue(getValue());
|
||||
}, [getValue]);
|
||||
|
||||
const set = useCallback(
|
||||
(newValue) => {
|
||||
setValue(newValue);
|
||||
trySet(key, newValue);
|
||||
},
|
||||
[key, setValue]
|
||||
);
|
||||
|
||||
const remove = () => {
|
||||
setValue(defaultValue as T);
|
||||
tryRemove(key);
|
||||
};
|
||||
|
||||
return [value, set, remove];
|
||||
}
|
||||
|
||||
export default useLocalStorage;
|
||||
|
||||
// A wrapper for "JSON.parse()"" to support "undefined" value
|
||||
function parseJSON<T>(value: any): T | undefined {
|
||||
try {
|
||||
return value === "undefined" ? undefined : JSON.parse(value ?? "");
|
||||
} catch (error) {
|
||||
console.log("parsing error on", { value });
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
ApiAdapterUtils,
|
||||
ApiRequests,
|
||||
characterActions,
|
||||
rulesEngineSelectors,
|
||||
syncTransactionActions,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { toastMessageActions } from "~/tools/js/Shared/actions";
|
||||
|
||||
export interface ExportPdfData {
|
||||
exportPdf: () => void;
|
||||
pdfUrl: string | null;
|
||||
isLoading: boolean;
|
||||
isFinished: boolean;
|
||||
}
|
||||
export const usePdfExport = (): ExportPdfData => {
|
||||
const [isFinished, setIsFinished] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||
const pdfData = useSelector(rulesEngineSelectors.getPdfExportData);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const exportPdf = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
let syncActivationId: string = "PDF-SYNC";
|
||||
dispatch(syncTransactionActions.activate(syncActivationId));
|
||||
|
||||
//This will update the character's spell data before export is created
|
||||
//(in case a user is in the builder and has not updated their class spells)
|
||||
dispatch(characterActions.loadLazyCharacterData());
|
||||
|
||||
const response = await ApiRequests.postCharacterPdf({
|
||||
exportData: JSON.stringify(pdfData),
|
||||
});
|
||||
const pdfUrl = ApiAdapterUtils.getResponseData(response);
|
||||
|
||||
if (pdfUrl) {
|
||||
setPdfUrl(pdfUrl);
|
||||
dispatch(syncTransactionActions.deactivate());
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
toastMessageActions.toastError("Error", "An unexpected error occurred.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsFinished(true);
|
||||
}
|
||||
}, []);
|
||||
return { exportPdf, pdfUrl, isLoading, isFinished };
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { useSidebar } from "~/contexts/Sidebar";
|
||||
import {
|
||||
SidebarAlignmentEnum,
|
||||
SidebarPlacementEnum,
|
||||
SidebarPositionInfo,
|
||||
} from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { SheetPositioningInfo } from "~/tools/js/CharacterSheet/typings";
|
||||
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
|
||||
|
||||
export const usePositioning = () => {
|
||||
const {
|
||||
sidebar: { isVisible, placement, alignment, width },
|
||||
} = useSidebar();
|
||||
|
||||
const getSheetPositioning = (): SheetPositioningInfo => {
|
||||
let offset: number = 0;
|
||||
if (!isVisible) {
|
||||
return { offset, left: offset };
|
||||
}
|
||||
|
||||
if (placement === SidebarPlacementEnum.FIXED) {
|
||||
offset = width / 2;
|
||||
}
|
||||
|
||||
return {
|
||||
offset,
|
||||
left: alignment === SidebarAlignmentEnum.LEFT ? offset : -1 * offset,
|
||||
};
|
||||
};
|
||||
|
||||
const getSidebarPositioning = (): SidebarPositionInfo => {
|
||||
const sheetDimensions = useSelector(appEnvSelectors.getDimensions);
|
||||
const sheetPosition = getSheetPositioning();
|
||||
|
||||
const { sheet, window } = sheetDimensions;
|
||||
|
||||
let posX: number = 0;
|
||||
let sidebarGutter: number = 15;
|
||||
let screenGutterSize: number = (window.width - sheet.width) / 2;
|
||||
|
||||
if (placement === SidebarPlacementEnum.FIXED) {
|
||||
posX = screenGutterSize + sheetPosition.offset - sidebarGutter - width;
|
||||
} else {
|
||||
screenGutterSize -= sidebarGutter;
|
||||
let overflowOffset: number =
|
||||
width > screenGutterSize ? width - screenGutterSize : 0;
|
||||
posX = screenGutterSize - width + overflowOffset;
|
||||
}
|
||||
|
||||
return {
|
||||
left: alignment === SidebarAlignmentEnum.LEFT ? posX : "auto",
|
||||
right: alignment === SidebarAlignmentEnum.RIGHT ? posX : "auto",
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
getSheetPositioning,
|
||||
getSidebarPositioning,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
RuleDataUtils as ruleDataUtils,
|
||||
ruleDataSelectors as s,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
/**
|
||||
* A hook that provides access to the rule data state without having to
|
||||
* import rules engine all over the place in your components. This reduces
|
||||
* touchpoints to the rules engine and makes it easier to refactor the rules
|
||||
* engine in the future.
|
||||
*/
|
||||
export const useRuleData = () => ({
|
||||
// Export all data coming from the RuleData selectors
|
||||
allData: useSelector(s.getAllData),
|
||||
abilityScoreDisplayTypes: useSelector(s.getAbilityScoreDisplayTypes),
|
||||
abilitySkills: useSelector(s.getAbilitySkills),
|
||||
activationTypes: useSelector(s.getActivationTypes),
|
||||
additionalLevelTypes: useSelector(s.getAdditionalLevelTypes),
|
||||
adjustmentDataTypes: useSelector(s.getAdjustmentDataTypes),
|
||||
adjustmentTypes: useSelector(s.getAdjustmentTypes),
|
||||
alignments: useSelector(s.getAlignments),
|
||||
aoeTypes: useSelector(s.getAoeTypes),
|
||||
armor: useSelector(s.getArmor),
|
||||
basicActions: useSelector(s.getBasicActions),
|
||||
baseWeaponReach: useSelector(s.getBaseWeaponReach),
|
||||
basicMaxStatScore: useSelector(s.getBasicMaxStatScore),
|
||||
challengeRatings: useSelector(s.getChallengeRatings),
|
||||
conditionTypes: useSelector(s.getConditionTypes),
|
||||
conditions: useSelector(s.getConditions),
|
||||
creatureGroupCategories: useSelector(s.getCreatureGroupCategories),
|
||||
creatureGroupFlags: useSelector(s.getCreatureGroupFlags),
|
||||
creatureGroups: useSelector(s.getCreatureGroups),
|
||||
creatureSizes: useSelector(s.getCreatureSizes),
|
||||
currencyData: useSelector(s.getCurrencyData),
|
||||
damageAdjustments: useSelector(s.getDamageAdjustments),
|
||||
damageTypes: useSelector(s.getDamageTypes),
|
||||
defaultArmorImageUrl: useSelector(s.getDefaultArmorImageUrl),
|
||||
defaultAttunedItemCountMax: useSelector(s.getDefaultAttunedItemCountMax),
|
||||
defaultGearImageUrl: useSelector(s.getDefaultGearImageUrl),
|
||||
defaultRacePortraitUrl: useSelector(s.getDefaultRacePortraitUrl),
|
||||
defaultWeaponImageUrl: useSelector(s.getDefaultWeaponImageUrl),
|
||||
diceValues: useSelector(s.getDiceValues),
|
||||
environments: useSelector(s.getEnvironments),
|
||||
initiativeScore: useSelector(s.getInitiativeScore),
|
||||
languageTypeId: useSelector(s.getLanguageTypeId),
|
||||
languages: useSelector(s.getLanguages),
|
||||
levelExperiencePoints: useSelector(s.getLevelExperiencePoints),
|
||||
levelProficiencyBonuses: useSelector(s.getLevelProficiencyBonuses),
|
||||
lifestyles: useSelector(s.getLifestyles),
|
||||
limitedUseResetTypes: useSelector(s.getLimitedUseResetTypes),
|
||||
longRestMinHitDiceUsedRecovered: useSelector(
|
||||
s.getLongRestMinimumHitDiceUsedRecovered
|
||||
),
|
||||
maxAttunedItemCountMax: useSelector(s.getMaxAttunedItemCountMax),
|
||||
maxCharacterLevel: useSelector(s.getMaxCharacterLevel),
|
||||
maxDeathsavesFail: useSelector(s.getMaxDeathsavesFail),
|
||||
maxDeathsavesSuccess: useSelector(s.getMaxDeathsavesSuccess),
|
||||
maxSpellLevel: useSelector(s.getMaxSpellLevel),
|
||||
maxStatScore: useSelector(s.getMaxStatScore),
|
||||
minAttunedItemCountMax: useSelector(s.getMinAttunedItemCountMax),
|
||||
minStatScore: useSelector(s.getMinStatScore),
|
||||
minimumHpTotal: useSelector(s.getMinimumHpTotal),
|
||||
minimumLimitedUseMaxUse: useSelector(s.getMinimumLimitedUseMaxUse),
|
||||
monsterSubTypes: useSelector(s.getMonsterSubTypes),
|
||||
monsterTypes: useSelector(s.getMonsterTypes),
|
||||
movements: useSelector(s.getMovements),
|
||||
multiClassSpellSlots: useSelector(s.getMultiClassSpellSlots),
|
||||
naturalActions: useSelector(s.getNaturalActions),
|
||||
noArmorAcAmount: useSelector(s.getNoArmorAcAmount),
|
||||
pactMagicMultiClassSpellSlots: useSelector(
|
||||
s.getPactMagicMultiClassSpellSlots
|
||||
),
|
||||
privacyTypes: useSelector(s.getPrivacyTypes),
|
||||
proficiencyGroups: useSelector(s.getProficiencyGroups),
|
||||
raceGroups: useSelector(s.getRaceGroups),
|
||||
rangeTypes: useSelector(s.getRangeTypes),
|
||||
ritualCastingTimeMinuteAddition: useSelector(
|
||||
s.getRitualCastingTimeMinuteAddition
|
||||
),
|
||||
rules: useSelector(s.getRules),
|
||||
senses: useSelector(s.getSenses),
|
||||
sharingTypes: useSelector(s.getSharingTypes),
|
||||
sourceCategories: useSelector(s.getSourceCategories),
|
||||
sources: useSelector(s.getSources),
|
||||
spellComponents: useSelector(s.getSpellComponents),
|
||||
spellConditionTypes: useSelector(s.getSpellConditionTypes),
|
||||
spellRangeTypes: useSelector(s.getSpellRangeTypes),
|
||||
statModifiers: useSelector(s.getStatModifiers),
|
||||
stats: useSelector(s.getStats),
|
||||
stealthCheckTypes: useSelector(s.getStealthCheckTypes),
|
||||
stringMartialArts: useSelector(s.getStringMartialArts),
|
||||
stringPactMagic: useSelector(s.getStringPactMagic),
|
||||
stringSpellCasting: useSelector(s.getStringSpellCasting),
|
||||
stringSpellEldritchBlast: useSelector(s.getStringSpellEldritchBlast),
|
||||
weapons: useSelector(s.getWeapons),
|
||||
weaponCategories: useSelector(s.getWeaponCategories),
|
||||
weaponProperties: useSelector(s.getWeaponProperties),
|
||||
weaponPropertyReachDistance: useSelector(s.getWeaponPropertyReachDistance),
|
||||
|
||||
// Export all utils from RuleDataUtils
|
||||
ruleDataUtils,
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
import { orderBy } from "lodash";
|
||||
|
||||
import {
|
||||
BuilderChoiceOptionContract,
|
||||
EntityRestrictionData,
|
||||
HelperUtils,
|
||||
HtmlSelectOptionGroup,
|
||||
RuleDataUtils,
|
||||
SimpleSourcedDefinitionContract,
|
||||
SourceMappingContract,
|
||||
SourceUtils,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
import { TypeScriptUtils } from "~/tools/js/Shared/utils";
|
||||
|
||||
import { useCharacterEngine } from "./useCharacterEngine";
|
||||
import { useRuleData } from "./useRuleData";
|
||||
|
||||
interface SourceCategoryGroup<T> {
|
||||
name: string;
|
||||
id: number;
|
||||
items: Array<T>;
|
||||
sortOrder: number | undefined;
|
||||
}
|
||||
|
||||
type SourceCategoryKey =
|
||||
| "avatarUrl"
|
||||
| "description"
|
||||
| "id"
|
||||
| "isEnabledByDefault"
|
||||
| "isHideable"
|
||||
| "isPartneredContent"
|
||||
| "isToggleable"
|
||||
| "name";
|
||||
|
||||
export const useSource = () => {
|
||||
const {
|
||||
ruleData,
|
||||
helperUtils: { lookupDataOrFallback },
|
||||
ruleDataUtils: { getSourceCategoryLookup },
|
||||
} = useCharacterEngine();
|
||||
const {
|
||||
ruleDataUtils: { getSourceDataLookup },
|
||||
} = useRuleData();
|
||||
const allSources = getSourceDataLookup(ruleData);
|
||||
const allSourceCategories = getSourceCategoryLookup(ruleData);
|
||||
|
||||
// Returns the source ID from a given source
|
||||
const getSourceId = (source: SourceMappingContract) => source?.sourceId;
|
||||
|
||||
const getSource = (sourceId: number | string) =>
|
||||
lookupDataOrFallback(allSources, sourceId);
|
||||
|
||||
const getSourceName = (sourceId: number | string) =>
|
||||
lookupDataOrFallback(allSources, sourceId)?.name;
|
||||
|
||||
const getSourceDescription = (sourceId: number | string) =>
|
||||
lookupDataOrFallback(allSources, sourceId)?.description;
|
||||
|
||||
// Source Category direct access with category id
|
||||
const getSourceCategory = (sourceCategoryId: number) =>
|
||||
lookupDataOrFallback(allSourceCategories, sourceCategoryId);
|
||||
|
||||
const getSourceCategoryDescription = (sourceCategoryId: number) =>
|
||||
getSourceCategory(sourceCategoryId)?.description;
|
||||
|
||||
// Takes an array of any entity that contains a sources key and returns an array of sorted SourceCategoryGroup representing each Source Category with the category data and an array of the entities that belong to that category.
|
||||
const getSourceCategoryGroups = <
|
||||
T extends {
|
||||
sources: SourceMappingContract[] | null;
|
||||
}
|
||||
>(
|
||||
items: Array<T>
|
||||
): Array<SourceCategoryGroup<T>> => {
|
||||
let groups: Array<SourceCategoryGroup<T>> = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const sourceContracts = item.sources ?? [];
|
||||
|
||||
//get the source data for each source contract
|
||||
const sources = sourceContracts
|
||||
.map((source) =>
|
||||
HelperUtils.lookupDataOrFallback(
|
||||
RuleDataUtils.getSourceDataLookup(ruleData),
|
||||
source.sourceId
|
||||
)
|
||||
)
|
||||
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
||||
|
||||
//get the source category name for the first source contract or default to Homebrew
|
||||
const sourceCategoryName =
|
||||
sources.length > 0 &&
|
||||
sources[0].sourceCategory &&
|
||||
sources[0].sourceCategory.name
|
||||
? sources[0].sourceCategory.name
|
||||
: "Homebrew";
|
||||
|
||||
// Search the availableGroupedOptions array for the source category
|
||||
const index = groups.findIndex(
|
||||
(element) => element.name === sourceCategoryName
|
||||
);
|
||||
|
||||
// If the source category wasn't found, add it as a new option group.
|
||||
if (index < 0) {
|
||||
groups.push({
|
||||
name: sourceCategoryName,
|
||||
id: sources[0]?.sourceCategory?.id || 0,
|
||||
sortOrder: sources[0]?.sourceCategory?.sortOrder,
|
||||
items: [item],
|
||||
});
|
||||
// Otherwise, add the option to the existing source category's array of options.
|
||||
} else {
|
||||
groups[index].items.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return orderBy(groups, "sortOrder");
|
||||
};
|
||||
|
||||
// Takes an array of any entity's DefinitionContract and returns an array of sorted HtmlSelectOptionGroup representing each Source Category and a sorted array of options.
|
||||
const getGroupedOptionsBySourceCategory = <
|
||||
T extends {
|
||||
sources: SourceMappingContract[] | null;
|
||||
name: string | null;
|
||||
id: number;
|
||||
description?: string | null;
|
||||
}
|
||||
>(
|
||||
items: Array<T>,
|
||||
optionValue?: number | null,
|
||||
entityRestrictionData?: EntityRestrictionData,
|
||||
labelFallback?: string
|
||||
): HtmlSelectOptionGroup[] => {
|
||||
return SourceUtils.getGroupedOptionsBySourceCategory(
|
||||
items,
|
||||
ruleData,
|
||||
optionValue,
|
||||
entityRestrictionData,
|
||||
labelFallback
|
||||
);
|
||||
};
|
||||
|
||||
//Given an array of choiceOptionContracts, return an array of SimpleSourcedDefinitionContracts (these are used to create grouped options in a select dropdown)
|
||||
const getSimpleSourcedDefinitionContracts = (
|
||||
choiceOptionContracts: Array<BuilderChoiceOptionContract>
|
||||
): Array<SimpleSourcedDefinitionContract> => {
|
||||
return SourceUtils.getSimpleSourcedDefinitionContracts(
|
||||
choiceOptionContracts
|
||||
);
|
||||
};
|
||||
|
||||
// Compares a source's category to a given match and returns a boolean value
|
||||
const matchSourceCategory = (
|
||||
sourceId: number,
|
||||
match: string,
|
||||
key: SourceCategoryKey = "name"
|
||||
) => {
|
||||
if (!sourceId) return false;
|
||||
|
||||
const source = lookupDataOrFallback(allSources, sourceId);
|
||||
if (!source) return false;
|
||||
|
||||
if (source.sourceCategory?.[key] === match) return true;
|
||||
};
|
||||
|
||||
return {
|
||||
allSources,
|
||||
getSource,
|
||||
getSourceId,
|
||||
getSourceCategory,
|
||||
getSourceCategoryDescription,
|
||||
getSourceDescription,
|
||||
getSourceName,
|
||||
matchSourceCategory,
|
||||
getGroupedOptionsBySourceCategory,
|
||||
getSimpleSourcedDefinitionContracts,
|
||||
getSourceCategoryGroups,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { getSubscriptionTier } from "../helpers/userApi";
|
||||
|
||||
export const FREE_TIER = "free";
|
||||
export const HERO_TIER = "hero";
|
||||
export const MASTER_TIER = "master";
|
||||
export const LEGENDARY_TIER = "legendary";
|
||||
|
||||
const useSubscriptionTier = (defaultSubscription?: string) => {
|
||||
const [subscriptionTier, setSubscriptionTier] = useState<string | undefined>(
|
||||
defaultSubscription
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getSubscriptionTier().then((tier) => {
|
||||
if (tier) {
|
||||
setSubscriptionTier(tier as unknown as string);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (subscriptionTier || FREE_TIER).toLowerCase();
|
||||
};
|
||||
|
||||
export default useSubscriptionTier;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MouseEvent, useCallback } from "react";
|
||||
|
||||
/**
|
||||
* Prevents click events from propagating to parent elements.
|
||||
* @param onClick The function to call when the element is clicked.
|
||||
* @returns A function that will prevent the click event from propagating.
|
||||
*/
|
||||
export const useUnpropagatedClick = (
|
||||
onClick?: (event: MouseEvent, ...args: unknown[]) => void
|
||||
) => {
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent, ...args: unknown[]) => {
|
||||
event.stopPropagation();
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
onClick?.(event, ...args);
|
||||
},
|
||||
[onClick]
|
||||
);
|
||||
|
||||
return handleClick;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import type { User } from "@dndbeyond/authentication-lib-js";
|
||||
|
||||
import { getUser } from "../helpers/userApi";
|
||||
|
||||
export interface AppUser extends User {
|
||||
displayName: string;
|
||||
id: string;
|
||||
name: string;
|
||||
roles: Array<string>;
|
||||
subscription: string;
|
||||
subscriptionTier: string;
|
||||
}
|
||||
/**
|
||||
* AppUserState has three states:
|
||||
* - undefined: We currently don't know if the user is authenticated or not. ie loading state
|
||||
* - null: user is not authenticated
|
||||
* - User: user is authenticated and is a registered user
|
||||
*/
|
||||
const useUserInitialState = undefined;
|
||||
type UseUserUnauthenticated = null;
|
||||
export type AppUserState =
|
||||
| AppUser
|
||||
| UseUserUnauthenticated
|
||||
| typeof useUserInitialState;
|
||||
|
||||
const useUser = () => {
|
||||
const [user, setUser] = useState<AppUserState>(useUserInitialState);
|
||||
|
||||
useEffect(() => {
|
||||
getUser()
|
||||
.then(setUser)
|
||||
.catch(() => {
|
||||
setUser(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default useUser;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { getUserId } from "../helpers/userApi";
|
||||
|
||||
const useUserId = (defaultUserId?: number) => {
|
||||
const [userId, setUserId] = useState<number | undefined>(defaultUserId);
|
||||
|
||||
useEffect(() => {
|
||||
getUserId().then((user) => {
|
||||
setUserId(user as unknown as number);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return userId;
|
||||
};
|
||||
|
||||
export default useUserId;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { getUserDisplayName } from "../helpers/userApi";
|
||||
|
||||
const useUserName = () => {
|
||||
const [userName, setUserName] = useState<string | null>("");
|
||||
|
||||
useEffect(() => {
|
||||
getUserDisplayName()
|
||||
.then(setUserName)
|
||||
.catch(() => {
|
||||
setUserName(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return userName;
|
||||
};
|
||||
|
||||
export default useUserName;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { getRoles } from "../helpers/userApi";
|
||||
|
||||
const useUserRoles = () => {
|
||||
const [userRoles, setUserRoles] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
getRoles()
|
||||
.then(setUserRoles)
|
||||
.catch(() => {
|
||||
setUserRoles([]);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return userRoles;
|
||||
};
|
||||
|
||||
export default useUserRoles;
|
||||
Reference in New Issue
Block a user