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,123 @@
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getComponentId(choice) {
|
||||
return choice.componentId;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getComponentTypeId(choice) {
|
||||
return choice.componentTypeId;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getDefaultSubtypes(choice) {
|
||||
var _a;
|
||||
return (_a = choice.defaultSubtypes) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getId(choice) {
|
||||
return choice.id;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function isInfinite(choice) {
|
||||
return choice.isInfinite;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function isOptional(choice) {
|
||||
return choice.isOptional;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getLabel(choice) {
|
||||
return choice.label;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getOptions(choice) {
|
||||
var _a;
|
||||
return (_a = choice.options) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getOptionIds(choice) {
|
||||
var _a;
|
||||
return (_a = choice.optionIds) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getDisplayOrder(choice) {
|
||||
return choice.displayOrder;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getOptionValue(choice) {
|
||||
return choice.optionValue;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getParentChoiceId(choice) {
|
||||
return choice.parentChoiceId;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getSubType(choice) {
|
||||
return choice.subType;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getType(choice) {
|
||||
return choice.type;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getDataOrigin(choice) {
|
||||
return choice.dataOrigin;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getUniqueKey(choice) {
|
||||
return getId(choice);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function getTagConstraints(choice) {
|
||||
var _a;
|
||||
return (_a = choice.tagConstraints) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { groupBy, orderBy } from 'lodash';
|
||||
import { DataOriginGenerators } from '../DataOrigin';
|
||||
import { HelperUtils } from '../Helper';
|
||||
import { getComponentId, getComponentTypeId, getDisplayOrder, getId, getLabel, getOptionIds, getParentChoiceId, getType, } from './accessors';
|
||||
/**
|
||||
*
|
||||
* @param choice
|
||||
*/
|
||||
export function generateDataOriginKey(choice) {
|
||||
return DataOriginGenerators.generateDataOriginKey(getComponentId(choice), getComponentTypeId(choice));
|
||||
}
|
||||
/**
|
||||
* @name generateChoiceComponentLookup
|
||||
* @param choices
|
||||
* @param choiceDefinitions
|
||||
* @description it takes choices and fills out the options via the optionIds and the choiceDefinitions and returns a lookup
|
||||
*/
|
||||
export function generateChoiceComponentLookup(choices, choiceDefinitions) {
|
||||
if (choiceDefinitions.length > 0) {
|
||||
choices.forEach((choice) => {
|
||||
var _a;
|
||||
// find the collection of choice definitions for this choice
|
||||
const options = ((_a = choiceDefinitions.find(({ id }) => id === `${getComponentTypeId(choice)}-${getType(choice)}`)) === null || _a === void 0 ? void 0 : _a.options) || null;
|
||||
const optionIds = getOptionIds(choice);
|
||||
if (options && optionIds.length) {
|
||||
// set existing list to empty array for phase 1 support
|
||||
choice.options = [];
|
||||
for (let i = 0; i < optionIds.length; i++) {
|
||||
const optionId = optionIds[i];
|
||||
// find the definition of the choice to add back to the list
|
||||
const option = options.find((c) => c.id === optionId);
|
||||
if (option) {
|
||||
choice.options.push(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return groupBy(choices, (choice) => generateDataOriginKey(choice));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param proficiencyModifiers
|
||||
* @param expertiseModifiers
|
||||
* @param languageModifiers
|
||||
* @param kenseiModifiers
|
||||
* @param abilityLookup
|
||||
* @param classSpellLists
|
||||
*/
|
||||
export function generateChoiceData(proficiencyModifiers, expertiseModifiers, languageModifiers, kenseiModifiers, abilityLookup, classSpellLists) {
|
||||
return {
|
||||
proficiencyModifiers,
|
||||
expertiseModifiers,
|
||||
languageModifiers,
|
||||
kenseiModifiers,
|
||||
abilityLookup,
|
||||
classSpellLists,
|
||||
};
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
* @param entityTypeId
|
||||
* @param choiceLookup
|
||||
* @param dataOriginType
|
||||
* @param primary
|
||||
* @param parent
|
||||
*/
|
||||
export function generateBaseChoices(id, entityTypeId, choiceLookup, dataOriginType, primary, parent = null) {
|
||||
const choices = HelperUtils.lookupDataOrFallback(choiceLookup, DataOriginGenerators.generateDataOriginKey(id, entityTypeId));
|
||||
if (!choices) {
|
||||
return [];
|
||||
}
|
||||
// create a sub choice lookup for later
|
||||
const subChoiceLookup = groupBy(choices.filter((choice) => getParentChoiceId(choice) !== null), (choice) => getParentChoiceId(choice));
|
||||
// get all choices that aren't children and sort them
|
||||
const orderedParentChoices = orderBy(choices.filter((choice) => getParentChoiceId(choice) === null), [getDisplayOrder, getType, (choice) => { var _a, _b; return (_b = (_a = getLabel(choice)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : null; }, getId]);
|
||||
// iterate over all non children choices, add choice, and then all ordered children after it into a flat array for parity
|
||||
const orderedChoices = [];
|
||||
orderedParentChoices.forEach((parentChoice) => {
|
||||
orderedChoices.push(parentChoice);
|
||||
const id = getId(parentChoice);
|
||||
if (id !== null) {
|
||||
let subChoices = HelperUtils.lookupDataOrFallback(subChoiceLookup, id);
|
||||
if (subChoices !== null) {
|
||||
let orderedSubChoices = orderBy(subChoices, [
|
||||
getDisplayOrder,
|
||||
getType,
|
||||
(choice) => { var _a, _b; return (_b = (_a = getLabel(choice)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : null; },
|
||||
getId,
|
||||
]);
|
||||
orderedChoices.push(...orderedSubChoices);
|
||||
}
|
||||
}
|
||||
});
|
||||
return orderedChoices.map((choice) => (Object.assign(Object.assign({}, choice), { dataOrigin: DataOriginGenerators.generateDataOrigin(dataOriginType, primary, parent) })));
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as ChoiceAccessors from './accessors';
|
||||
import * as ChoiceGenerators from './generators';
|
||||
import * as ChoiceTypings from './typings';
|
||||
import * as ChoiceUtils from './utils';
|
||||
export * from './typings';
|
||||
export { ChoiceAccessors, ChoiceGenerators, ChoiceUtils };
|
||||
export default Object.assign(Object.assign(Object.assign(Object.assign({}, ChoiceAccessors), ChoiceGenerators), ChoiceTypings), ChoiceUtils);
|
||||
@@ -0,0 +1,211 @@
|
||||
import { orderBy } from 'lodash';
|
||||
import { TypeScriptUtils } from "../../utils";
|
||||
import { ClassAccessors } from '../Class';
|
||||
import { AbilityStatEnum, BuilderChoiceSubtypeEnum } from '../Core';
|
||||
import { DataOriginTypeEnum } from '../DataOrigin';
|
||||
import { EntityUtils } from '../Entity';
|
||||
import { HelperUtils } from '../Helper';
|
||||
import { ModifierAccessors } from '../Modifier';
|
||||
import { SourceUtils } from '../Source';
|
||||
import { SpellUtils } from '../Spell';
|
||||
import { getDefaultSubtypes, getOptions, getOptionValue, isOptional } from './accessors';
|
||||
export function isOnlyDefaultSelected(choice) {
|
||||
const defaultSubtypes = getDefaultSubtypes(choice);
|
||||
if (defaultSubtypes.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (!getOptionValue(choice)) {
|
||||
return false;
|
||||
}
|
||||
const selectedOption = getSelectedOption(choice);
|
||||
if (selectedOption) {
|
||||
return selectedOption.label === defaultSubtypes[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function getSelectedOption(choice) {
|
||||
return getOptions(choice).find((option) => option.id === getOptionValue(choice));
|
||||
}
|
||||
export function isOptionSelected(choice) {
|
||||
return !!getSelectedOption(choice);
|
||||
}
|
||||
export function isTodo(choice) {
|
||||
return !isOptional(choice) && getOptionValue(choice) === null;
|
||||
}
|
||||
// Given a Choice, return options for a select element that are potentially grouped by sourceCategory
|
||||
//used in DetailChoiceFeat.tsx for feat choices
|
||||
export function getSortedChoiceOptionsInfo(choice, ruleData, entityRestrictionData) {
|
||||
var _a;
|
||||
const options = getOptions(choice);
|
||||
let description;
|
||||
// If every option in the array has a null sourceId, ignore sorting by sourceId.
|
||||
// If any option has a non-null sourceId, sort by sourceId.
|
||||
const shouldSortBySourceId = options.some((option) => option.hasOwnProperty('sourceId') && option.sourceId !== null);
|
||||
let featChoiceOptions;
|
||||
//If the options have a sourceId, group them by Source Category (for now this should only be BuilderChoiceTypeEnum.ENTITY_SPELL_OPTION - we also have to do this in FeatureChoice for spell options)
|
||||
if (shouldSortBySourceId) {
|
||||
// Map over the options to mock parts of a DefinitionContract.
|
||||
const groupedOptions = SourceUtils.getSimpleSourcedDefinitionContracts(options);
|
||||
//If there is a chosen option, set detailChoiceDesc to its description.
|
||||
const chosenOption = options.find((option) => option.id === getOptionValue(choice));
|
||||
description = (_a = chosenOption === null || chosenOption === void 0 ? void 0 : chosenOption.description) !== null && _a !== void 0 ? _a : '';
|
||||
featChoiceOptions = SourceUtils.getGroupedOptionsBySourceCategory(groupedOptions, ruleData, chosenOption === null || chosenOption === void 0 ? void 0 : chosenOption.id, entityRestrictionData);
|
||||
}
|
||||
else {
|
||||
featChoiceOptions = options.map((option) => (Object.assign(Object.assign({}, option), { value: option.id })));
|
||||
featChoiceOptions = orderBy(featChoiceOptions, 'label');
|
||||
}
|
||||
return {
|
||||
options: featChoiceOptions,
|
||||
description,
|
||||
};
|
||||
}
|
||||
// filter Choice options based on modifiers and default subtypes that identify when a user has made a choice from another source. We filter those options out or we append the source data origin name next to the option label
|
||||
export function getRemainingOptions(options, modifiers, defaultSubtypes, showBackgroundProficiencyOptions, optionValue) {
|
||||
let remainingSubtypes = [];
|
||||
let selectedOption = options.find((option) => option.value === optionValue);
|
||||
//if there is a default option set, the default option is granted automatically.
|
||||
//defaultSubtypes would be an array of remaining options and we need to filter out anything that is already found in a modifier from another source
|
||||
if (defaultSubtypes) {
|
||||
if (defaultSubtypes.length > 1) {
|
||||
defaultSubtypes.forEach((defaultSubtype) => {
|
||||
if (!modifiers.find((modifier) => modifier.friendlySubtypeName === defaultSubtype) ||
|
||||
(selectedOption && selectedOption.label === defaultSubtype)) {
|
||||
remainingSubtypes.push(defaultSubtype);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (defaultSubtypes.length === 1 &&
|
||||
!modifiers.find((modifier) => modifier.friendlySubtypeName === defaultSubtypes[0])) {
|
||||
remainingSubtypes = [...defaultSubtypes];
|
||||
}
|
||||
}
|
||||
// If there are any remaining subtypes, only show those
|
||||
if (remainingSubtypes.length) {
|
||||
options = options.filter((option) => remainingSubtypes.includes(option.label ? option.label : '') || optionValue === option.value);
|
||||
}
|
||||
else {
|
||||
// If there aren't any remaining subtype defaults, just show the normal list of options
|
||||
if (modifiers.length) {
|
||||
options = options.reduce((acc, option) => {
|
||||
// Find any existing modifiers that are the current option
|
||||
let optionModifiers = modifiers.filter((modifier) => modifier.friendlySubtypeName === option.label);
|
||||
let backgroundOptionModifier = null;
|
||||
if (showBackgroundProficiencyOptions && optionModifiers.length) {
|
||||
// If there are any existing modifiers, find out if any of them are from a background
|
||||
const foundBackgroundOptionModifier = optionModifiers.find((modifier) => !ModifierAccessors.isGranted(modifier) &&
|
||||
ModifierAccessors.getDataOriginType(modifier) === DataOriginTypeEnum.BACKGROUND);
|
||||
if (foundBackgroundOptionModifier) {
|
||||
backgroundOptionModifier = foundBackgroundOptionModifier;
|
||||
}
|
||||
}
|
||||
if (!optionModifiers.length || backgroundOptionModifier || optionValue === option.value) {
|
||||
let value = typeof option.value === 'string' ? HelperUtils.parseInputInt(option.value) : option.value;
|
||||
//append the dataOrigin of the modifier (background) to the option label so the user knows where the option was selected
|
||||
if (value !== null) {
|
||||
acc.push(Object.assign(Object.assign({}, option), { value: value, label: backgroundOptionModifier
|
||||
? `${option.label} (${EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(backgroundOptionModifier))})`
|
||||
: option.label }));
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
//TODO this was abstracted from DetailChoice.tsx and needs to be broken down to smaller utils and simplified
|
||||
//TODO also need to remove the Array<any> type when gathering the renderOptions and fix the typing
|
||||
export function getSortedRenderOptions(options, classId, subType, entityRestrictionData, languages, ruleData, defaultSubtypes, showBackgroundProficiencyOptions, choiceInfo, optionValue) {
|
||||
const { classSpellLists, proficiencyModifiers, languageModifiers, expertiseModifiers, kenseiModifiers, abilityLookup, } = choiceInfo;
|
||||
// TODO need to get rid of this any and fix the typing
|
||||
// let renderOptions: Array<FeatureChoiceOption | HtmlSelectOptionGroup> = options;
|
||||
let renderOptions = options;
|
||||
switch (subType) {
|
||||
case BuilderChoiceSubtypeEnum.PROFICIENCY:
|
||||
renderOptions = getRemainingOptions(renderOptions, [...proficiencyModifiers, ...expertiseModifiers], defaultSubtypes, showBackgroundProficiencyOptions, optionValue);
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.LANGUAGE:
|
||||
//handle languages based on other language choices made
|
||||
renderOptions = getRemainingOptions(renderOptions, languageModifiers, defaultSubtypes, showBackgroundProficiencyOptions, optionValue);
|
||||
//create lookup from ruledata - name: rpgSourceId
|
||||
const languageNameLookup = languages.reduce((acc, curr) => {
|
||||
var _a;
|
||||
return Object.assign(Object.assign({}, acc), { [(_a = curr.name) !== null && _a !== void 0 ? _a : '']: curr.rpgSourceId });
|
||||
}, {});
|
||||
//transform language options to choiceOptionContracts to attach sourceId for grouping by Source Category
|
||||
const choiceOptionContracts = renderOptions.map((option) => {
|
||||
return {
|
||||
label: option.label,
|
||||
id: option.id,
|
||||
description: option.description,
|
||||
sourceId: option.label ? HelperUtils.lookupDataOrFallback(languageNameLookup, option.label) : null,
|
||||
};
|
||||
});
|
||||
const groupedOptions = SourceUtils.getSimpleSourcedDefinitionContracts(choiceOptionContracts);
|
||||
//group languages by source category
|
||||
renderOptions = SourceUtils.getGroupedOptionsBySourceCategory(groupedOptions, ruleData, optionValue, entityRestrictionData, 'Other');
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.KENSEI:
|
||||
renderOptions = getRemainingOptions(renderOptions, kenseiModifiers, defaultSubtypes, showBackgroundProficiencyOptions, optionValue);
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.EXPERTISE:
|
||||
renderOptions = renderOptions.filter((option) => (proficiencyModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === option.label) &&
|
||||
!expertiseModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === option.label)) ||
|
||||
optionValue === option.value);
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.EXPERTISE_NO_REQUIREMENT:
|
||||
renderOptions = renderOptions.filter((option) => !expertiseModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === option.label) || optionValue === option.value);
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.KNOWN_SPELLS:
|
||||
const spellList = classSpellLists.find((classSpellList) => ClassAccessors.getId(classSpellList.charClass) === classId);
|
||||
if (spellList) {
|
||||
renderOptions = renderOptions
|
||||
.map((topOption) => {
|
||||
if ('options' in topOption && topOption.options) {
|
||||
return Object.assign(Object.assign({}, topOption), { options: topOption.options.filter((option) => SpellUtils.isSpellKnown(spellList, Number(option.value)) ||
|
||||
optionValue === option.value) });
|
||||
}
|
||||
else {
|
||||
const singleOption = topOption;
|
||||
return SpellUtils.isSpellKnown(spellList, Number(singleOption.value)) ||
|
||||
optionValue === singleOption.value
|
||||
? singleOption
|
||||
: null;
|
||||
}
|
||||
})
|
||||
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
||||
}
|
||||
break;
|
||||
case BuilderChoiceSubtypeEnum.ABILITY_SCORE: {
|
||||
renderOptions = renderOptions.filter((option) => {
|
||||
let abilityId = null;
|
||||
switch (option.label) {
|
||||
case 'Charisma Score':
|
||||
abilityId = AbilityStatEnum.CHARISMA;
|
||||
break;
|
||||
case 'Constitution Score':
|
||||
abilityId = AbilityStatEnum.CONSTITUTION;
|
||||
break;
|
||||
case 'Dexterity Score':
|
||||
abilityId = AbilityStatEnum.DEXTERITY;
|
||||
break;
|
||||
case 'Intelligence Score':
|
||||
abilityId = AbilityStatEnum.INTELLIGENCE;
|
||||
break;
|
||||
case 'Strength Score':
|
||||
abilityId = AbilityStatEnum.STRENGTH;
|
||||
break;
|
||||
case 'Wisdom Score':
|
||||
abilityId = AbilityStatEnum.WISDOM;
|
||||
break;
|
||||
}
|
||||
return !abilityId || (abilityId && !abilityLookup[abilityId].isMaxed) || optionValue === option.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return renderOptions.length > 0 && renderOptions[0].hasOwnProperty('optGroupLabel')
|
||||
? orderBy(renderOptions, 'sortOrder')
|
||||
: orderBy(renderOptions, 'label');
|
||||
}
|
||||
Reference in New Issue
Block a user