Convert Icon to PureComponent and fix fallback
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
- [ ] Fix: Initial load `pointString` validation (make sure all talents are valid and their deps are met)
|
- [ ] 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: Navigating between talent links for same class does not trigger re-render
|
||||||
- [ ] Fix: Icon image fallback if not locally known
|
|
||||||
|
|
||||||
- [ ] Styling:
|
- [ ] Styling:
|
||||||
- [ ] SCSS: Normalize
|
- [ ] SCSS: Normalize
|
||||||
|
|||||||
+87
-56
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import './Icon.scss'
|
import './Icon.scss'
|
||||||
|
import React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name?: string
|
name?: string
|
||||||
@@ -11,60 +11,91 @@ interface Props {
|
|||||||
|
|
||||||
const NOT_FOUND_ICON = 'inv_misc_questionmark'
|
const NOT_FOUND_ICON = 'inv_misc_questionmark'
|
||||||
|
|
||||||
export const Icon = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
|
const makeUrl = (name: string, size: string, useFallback = false): string => {
|
||||||
const {
|
if (useFallback) {
|
||||||
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 (
|
|
||||||
<div className={cn} ref={ref} {...rest}>
|
|
||||||
{url &&
|
|
||||||
<div className="icon__bg" style={{ backgroundImage: `url(${url})` }} />
|
|
||||||
}
|
|
||||||
<div className="icon__frame" />
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg`
|
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<Props> {
|
||||||
|
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 (
|
||||||
|
<div className={cn} {...rest}>
|
||||||
|
{url &&
|
||||||
|
<div className="icon__bg" style={{ backgroundImage: `url(${url})` }} />
|
||||||
|
}
|
||||||
|
<div className="icon__frame" />
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user