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 { name: string; id: number; items: Array; 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 ): Array> => { let groups: Array> = []; 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, 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 ): Array => { 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, }; };