import * as classnames from 'classnames'; import * as React from 'react'; import FrameType from '../FrameType'; import Head from './ApiHead'; import Body from './ApiBody'; import { Extended } from './ApiBody/Stats'; export interface ItemProps { // only required for rare and unique items name?: string; typeLine: React.ReactNode; identified: boolean; category: { [key: string]: string[] }; frameType: FrameType; elder?: boolean; shaper?: boolean; corrupted?: boolean; utilityMods?: React.ReactNode[]; implicitMods?: React.ReactNode[]; explicitMods?: React.ReactNode[]; craftedMods?: React.ReactNode[]; enchantMods?: React.ReactNode[]; properties?: LineContent[]; requirements?: LineContent[]; } export interface LineContent { name: React.ReactNode; values: Array<[string, number]>; displayMode: number; type?: number; } export interface Props { classname?: string; extended?: Extended; item: ItemProps; width?: number; } export interface State { width?: number; props: Props; } /** * Component for items from poe official apis assuming an react-inlt.IntlProvider * in the component tree */ export default class ApiPopupIntl extends React.PureComponent { public static defaultProps = { classname: 'poe-item', }; public static DIMENSIONS = { min_width: 262.88032454361, max_width: 742.39350912779, max_description_width: 365.11156186613, }; public static getDerivedStateFromProps(next_props: Props, prevState: State) { // prev_props hack if (next_props === prevState.props) { return null; } else { // mark width for recomputation because we will render a different item return { width: next_props.width, }; } } public static assertValidProps( item: ItemProps, onError: (err: string) => void, ) { if (item.elder && item.shaper) { onError('item can either be shaped or elder item but not both'); } } private ref: React.RefObject; constructor(props: Props) { super(props); if (process.env.NODE_ENV !== 'production') { ApiPopupIntl.assertValidProps(props.item, console.warn); } this.ref = React.createRef(); this.state = { width: props.width, props, }; } public componentDidMount() { // https://reactjs.org/docs/react-component.html#componentdidmount // see section about measure a DOM node if (this.props.width === undefined) { // not fixed mode so compute this.computeWidth(); } } public componentDidUpdate(prev_props: Props, prev_state: State) { if (this.state.width === undefined) { this.computeWidth(); } } public render() { const { classname, extended, item } = this.props; const { width } = this.state; const style = { width: width === undefined ? 'auto' : width, minWidth: ApiPopupIntl.DIMENSIONS.min_width, maxWidth: ApiPopupIntl.DIMENSIONS.max_width, }; return (
); } /** * calculates the width of the popup as described in reasearch/popup-width */ private computeWidth() { const element = this.ref.current; if (element !== null) { const width = Math.max( ...[...element.querySelectorAll('.lc')].map(lc => { return outerWidth(lc as HTMLElement) + 2; }), ...[ ...element.querySelectorAll('.descrText span, .secDescrText span'), ].map(el => { // Todo "o.MaxDescriptionWidth" return Math.min( ApiPopupIntl.DIMENSIONS.max_description_width, outerWidth(el as HTMLElement), ); }), ); this.setState({ width }); } } private hasExtendedModView() { return this.props.extended != null; } } /** * jQuery outerWidth used on poe website * taken from http://youmightnotneedjquery.com/ search query: outerwidth * @param element */ function outerWidth(element: HTMLElement) { const width = element.offsetWidth; const style = getComputedStyle(element); return ( width + parseInt(style.marginLeft || '0', 10) + parseInt(style.marginRight || '0', 10) ); }