import { formatValue } from 'poe-i18n'; import * as React from 'react'; import { FormattedMessage, injectIntl, InjectedIntlProps } from 'react-intl'; import { Omit } from 'utility-types'; import * as ItemProps from './props'; import TypeLineIntl from './TypeLineIntl'; import ApiPopupIntl from '../ApiPopupIntl'; import FrameType from '../../FrameType'; import { minMaxToString, valueNotZero, augmentableNotZero, ROLLABLE_VALUE_MESSAGE, } from '../../../util/value'; import { asPercentString } from '../../../util/number'; export type ApiProps = PropsType; // requirements below that threshold will not be displayed const MIN_ATTRIBUTE_REQUIREMENTS = 14; // otherwise ts merges item props => Props.item = ApiProps.item & props.Item export type Props = Omit & { item: ItemProps.Item; }; /** * HOC that simply maps our own Item interface to the interface thats used * by offical APIs provided by GGG * * We created our own because the offical interface mixes properties that * describe what is displayed with properties that describe how it is displayed * our own interface aims at properties that only displays what is displayed. * See also Model vs. View */ class ItemPopupIntl extends React.PureComponent { public static assertValidProps( item: ItemProps.Item, onError: (err: string) => void, ) { if (item.name === undefined && item.rarity === 'rare') { onError('rare items need a name in addition to the name of the baseitem'); } if (item.base.name === undefined && item.base.id === undefined) { onError( 'You either have to provide an explicit basename or the id of the baseitem to enable i18n.', ); } if (item.elder && item.shaper) { onError('item can either be shaped or elder item but not both'); } } constructor(props: Props & InjectedIntlProps) { super(props); if (process.env.NODE_ENV !== 'production') { ItemPopupIntl.assertValidProps(props.item, console.warn); } } public render() { const { item, ...props } = this.props; return ; } private toApiProps(item: ItemProps.Item): ApiProps['item'] { return { corrupted: item.corrupted, name: item.name, typeLine: this.typeLine(item), frameType: this.frameType(item), identified: true, elder: item.elder, shaper: item.shaper, properties: this.apiProperties(item), requirements: this.apiRequirements(item), category: this.apiCategory(item), craftedMods: item.craftedStats, enchantMods: item.enchantStats, explicitMods: item.explicitStats, implicitMods: item.implicitStats, utilityMods: item.utilityStats, }; } private apiCategory(item: ItemProps.Item): ApiProps['item']['category'] { return {}; // TODO: requires extendet baseitem interface } private apiProperties(item: ItemProps.Item): ApiProps['item']['properties'] { const properties: ApiProps['item']['properties'] = []; const { quality } = item; if (valueNotZero(quality)) { properties.push({ name: ( ), values: [[`${quality}%`, 1]], displayMode: 0, }); } if (ItemProps.isArmourProperties(item)) { properties.push(...(this.apiArmourProperties(item) || [])); } else if (ItemProps.isWeaponProperties(item)) { properties.push(...(this.apiWeaponProperties(item) || [])); } return properties; } private apiArmourProperties( armour: ItemProps.ArmourProperties, ): ApiProps['item']['properties'] { const properties: ApiProps['item']['properties'] = []; const defences: Array< ['armour' | 'energy_shield' | 'evasion', React.ReactNode] > = [ [ 'armour', , ], [ 'energy_shield', , ], [ 'evasion', , ], ]; defences.forEach(([key, human]) => { const prop = armour[key]; if (augmentableNotZero(prop)) { properties.push({ name: human, values: [ [ formatValue(prop.value, { message: ROLLABLE_VALUE_MESSAGE }), prop.augmented ? 1 : 0, ], ], displayMode: 0, }); } }); if (ItemProps.isShieldProperties(armour)) { const { block } = armour; if (augmentableNotZero(block)) { properties.push({ name: , values: [ [ formatValue(block.value, { message: `${ROLLABLE_VALUE_MESSAGE}%`, }), block.augmented ? 1 : 0, ], ], displayMode: 0, }); } } return properties; } private apiWeaponProperties( weapon: ItemProps.WeaponProperties, ): ApiProps['item']['properties'] { const { intl: { formatMessage } } = this.props; const properties: ApiProps['item']['properties'] = []; const { physical_damage, cold_damage, lightning_damage, fire_damage, chaos_damage, crit, aps, range, } = weapon; // Physical Damage if (augmentableNotZero(physical_damage)) { properties.push({ name: ( ), values: [ [ minMaxToString(physical_damage.value, formatMessage), physical_damage.augmented ? 1 : 0, ], ], displayMode: 0, }); } // Elemental Damage const elemental_damage: Array<[string, number]> = []; if (valueNotZero(fire_damage)) { elemental_damage.push([minMaxToString(fire_damage, formatMessage), 4]); } if (valueNotZero(cold_damage)) { elemental_damage.push([minMaxToString(cold_damage, formatMessage), 5]); } if (valueNotZero(lightning_damage)) { elemental_damage.push([ minMaxToString(lightning_damage, formatMessage), 6, ]); } if (elemental_damage.length > 0) { properties.push({ name: ( ), values: elemental_damage, displayMode: 0, }); } // Chaos Damage if (valueNotZero(chaos_damage)) { properties.push({ name: ( ), values: [[minMaxToString(chaos_damage, formatMessage), 7]], displayMode: 0, }); } // Range if (augmentableNotZero(range)) { properties.push({ name: ( ), values: [ [ formatValue(range.value, { message: ROLLABLE_VALUE_MESSAGE, }), range.augmented ? 1 : 0, ], ], displayMode: 0, }); } // Crit if (augmentableNotZero(crit)) { properties.push({ name: ( ), values: [ [ formatValue(crit.value, { message: ROLLABLE_VALUE_MESSAGE, formatter: { id: 'divide_by_one_hundred_2dp', arg: 1, }, }), crit.augmented ? 1 : 0, ], ], displayMode: 0, }); } // APS if (augmentableNotZero(aps)) { properties.push({ name: ( ), values: [ [ formatValue(aps.value, { message: ROLLABLE_VALUE_MESSAGE, formatter: { id: 'divide_by_one_hundred_2dp', arg: 1, }, }), aps.augmented ? 1 : 0, ], ], displayMode: 0, }); } return properties; } private apiRequirements( item: ItemProps.Item, ): ApiProps['item']['requirements'] { const { requirements: item_requirements } = item; if (item_requirements === undefined) { return []; } const requirements: ApiProps['item']['requirements'] = []; const { level, dexterity, intelligence, strength } = item_requirements; if (augmentableNotZero(level)) { requirements.push({ name: , values: [ [ formatValue(level.value, { message: ROLLABLE_VALUE_MESSAGE, }), level.augmented ? 1 : 0, ], ], displayMode: 0, }); } if ( augmentableNotZero(dexterity) && dexterity.value > MIN_ATTRIBUTE_REQUIREMENTS ) { requirements.push({ name: , values: [ [ formatValue(dexterity.value, { message: ROLLABLE_VALUE_MESSAGE, }), dexterity.augmented ? 1 : 0, ], ], displayMode: 1, }); } if ( augmentableNotZero(intelligence) && intelligence.value > MIN_ATTRIBUTE_REQUIREMENTS ) { requirements.push({ name: , values: [ [ formatValue(intelligence.value, { message: ROLLABLE_VALUE_MESSAGE, }), intelligence.augmented ? 1 : 0, ], ], displayMode: 1, }); } if ( augmentableNotZero(strength) && strength.value > MIN_ATTRIBUTE_REQUIREMENTS ) { requirements.push({ name: , values: [ [ formatValue(strength.value, { message: ROLLABLE_VALUE_MESSAGE, }), strength.augmented ? 1 : 0, ], ], displayMode: 1, }); } return requirements; } private typeLine(item: ItemProps.Item): ApiProps['item']['typeLine'] { const { id, name } = item.base; const { prefix, suffix } = item; if (id != null) { return ( ); } else { return name; } } private frameType(item: ItemProps.Item): FrameType { switch (item.rarity) { case 'normal': return FrameType.normal; case 'magic': return FrameType.magic; case 'rare': return FrameType.rare; case 'unique': return FrameType.unique; } throw new Error('could not determine frameType from item'); } } export default injectIntl(ItemPopupIntl);