diff --git a/TODO.md b/TODO.md index 6b51a7c..c404e2f 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,6 @@ - [ ] Fix: Initial load `pointString` validation (make sure all talents are valid and their deps are met) - [ ] Fix: Navigating between talent links for same class does not trigger re-render -- [ ] Fix: Icon image fallback if not locally known - [ ] Styling: - [ ] SCSS: Normalize diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index 6f76eee..92cb8b5 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react' -import classNames from 'classnames' import './Icon.scss' +import React from 'react' +import classNames from 'classnames' interface Props { name?: string @@ -11,60 +11,91 @@ interface Props { const NOT_FOUND_ICON = 'inv_misc_questionmark' -export const Icon = React.forwardRef((props, ref) => { - const { - name: defaultName, - size = 'medium', - golden = false, - children, - className, - ...rest - } = props - const [hasLoadedImage, setLoadedImage] = useState(false) - const [fadeIn, setFadeIn] = useState(false) - const [name, setName] = useState(defaultName) - - const bgSize = size !== 'small' ? 'large' : 'medium' - const url = name && iconUrl(name, bgSize) - - const start = Date.now() - - useEffect(() => { - if (!url) return - const img = new Image() - img.onload = () => { - const loadTime = Date.now() - start - if (loadTime >= 300) { - setFadeIn(true) - } - setLoadedImage(true) - } - img.onerror = () => setName(NOT_FOUND_ICON) - img.src = url - }, [url, start]) - - const cn = classNames('icon', `icon--${size}`, className, { - 'icon--golden': golden, - 'icon--loaded': hasLoadedImage, - 'icon--fade-in': fadeIn, - }) - - return ( -
- {url && -
- } -
- {children} -
- ) -}) - -// TODO: Fallback is broken due to no longer using require() -const iconUrl = (name: string, size: string): string => { - try { - return `${process.env.PUBLIC_URL}/images/icons/${size}/${name}.jpg` - } catch (e) { +const makeUrl = (name: string, size: string, useFallback = false): string => { + if (useFallback) { return `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg` } + return `${process.env.PUBLIC_URL}/images/icons/${size}/${name}.jpg` +} + +export class Icon extends React.PureComponent { + static defaultProps = { + size: 'medium', + golden: false + } + + img: HTMLImageElement = undefined + + state = { + fadeIn: false, + url: '' + } + + componentDidMount() { + this.loadImage(this.props.name) + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.name !== this.props.name) { + this.loadImage(this.props.name) + } + } + + componentWillUnmount() { + if (this.img) { + this.img.onload = null + this.img.onerror = null + this.img.src = '' + } + } + + loadImage = (icon: string, useFallback = false): void => { + this.setState({ url: '', fadeIn: false }) + const bgSize = this.props.size !== 'small' ? 'large' : 'medium' + const url = icon && makeUrl(icon, bgSize, useFallback) + if (!url) return + + if (this.img) { + this.img.onload = null + this.img.onerror = null + this.img.src = '' + } + + const start = Date.now() + this.img = new Image() + this.img.onload = () => { + const loadTime = Date.now() - start + this.setState({ url, fadeIn: loadTime >= 300 }) + this.img = undefined + } + this.img.onerror = () => { + if (url.indexOf('wow.zamimg') === -1) { + this.loadImage(icon, true) + } else { + this.loadImage(NOT_FOUND_ICON) + } + } + this.img.src = url + } + + render() { + const { size, golden, className, ...rest } = this.props + const { fadeIn, url } = this.state + + const cn = classNames('icon', `icon--${size}`, className, { + 'icon--golden': golden, + 'icon--loaded': !!url, + 'icon--fade-in': fadeIn, + }) + + return ( +
+ {url && +
+ } +
+ {this.props.children} +
+ ) + } } \ No newline at end of file