Add talent decoding and spiffing up icon frames

This commit is contained in:
Melvin Valster
2019-07-22 21:16:55 +02:00
parent 2f99089377
commit 9d91bc32fb
18 changed files with 269 additions and 82 deletions
+22 -5
View File
@@ -13,9 +13,9 @@ import { History } from 'history'
interface Props {
selectedClass: string
history: History
initialTalents?: Map<number, number>
}
// const EMPTY_TALENTS = Map<number, number>()
const EMPTY_TALENTS = Map<number, number>()
// .set(30, 5)
// .set(26, 5)
@@ -33,6 +33,13 @@ export class Calculator extends React.PureComponent<Props> {
knownTalents: EMPTY_TALENTS
}
componentDidMount() {
if (this.props.initialTalents) {
this.setState({ knownTalents: this.props.initialTalents })
this.updateURL(this.props.initialTalents)
}
}
componentDidUpdate(prevProps: Props) {
if (prevProps.selectedClass !== this.props.selectedClass) {
this.setState({
@@ -40,17 +47,22 @@ export class Calculator extends React.PureComponent<Props> {
})
}
}
updateURL(knownTalents: Map<number, number>) {
const { selectedClass } = this.props
const pointString = encodeKnownTalents(knownTalents, selectedClass)
this.props.history.replace(`/${selectedClass}` + (pointString ? `/${pointString}` : ''))
}
handleTalentPress = (specId: number, talentId: number, modifier: 1 | -1) => {
const { selectedClass } = this.props
const talent = talentsBySpec[specId][talentId]
console.log('Clicked talent: ' + talentId)
const newKnownTalents = modifyTalentPoint(this.state.knownTalents, talent, modifier)
if (newKnownTalents !== this.state.knownTalents) {
this.updateURL(newKnownTalents)
}
this.setState({ knownTalents: newKnownTalents })
const pointString = encodeKnownTalents(newKnownTalents, selectedClass)
this.props.history.replace(`/${selectedClass}` + (pointString ? `/${pointString}` : ''))
}
render() {
@@ -77,6 +89,11 @@ export class Calculator extends React.PureComponent<Props> {
<div className="calculator__points">
Points: {availablePoints}
</div>
<div>
<a href="/shaman/-5505000055523051-55">Shaman test</a>
<a href="/shaman/-5595000055523051-55">Shaman test broken</a>
</div>
</div>
)
}
+4 -1
View File
@@ -29,7 +29,10 @@ export class ClassPicker extends React.PureComponent<Props> {
{Object.values(classByName).map((c) =>
<li key={c.id} className={classNameForItem(c, selected)}>
<Link to={`/${c.name.toLowerCase()}`} title={c.name}>
<Icon name={c.icon} />
<Icon
name={c.icon}
golden={selected === c.name.toLowerCase()}
/>
</Link>
</li>
)}
+40 -1
View File
@@ -1,10 +1,49 @@
.icon {
position: relative;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-size: contain;
background-color: #222;
&:hover {
.icon__bg {
box-shadow: inset 0px 0px 6px 3px rgba(99, 150, 214, .8);
}
}
&--medium {
width: 40px;
height: 40px;
border-radius: 5px;
.icon__bg {
width: 36px;
height: 36px;
top: 2px;
left: 2px;
}
}
&--golden {
.icon__frame {
background-image: url('/images/icons/large/gold.png');
}
}
&__bg {
position: absolute;
background-size: cover;
border-radius: 5px;
}
&__frame {
position: absolute;
width: 44px;
height: 44px;
top: -2px;
left: -2px;
background-image: url('/images/icons/large/default.png');
background-repeat: no-repeat;
background-size: contain;
}
}
+17 -4
View File
@@ -1,17 +1,30 @@
import React, { FC } from 'react'
import classNames from 'classnames'
import './Icon.scss'
interface Props {
name: string
size?: 'small' | 'medium' | 'large'
golden?: boolean
}
export const Icon: FC<Props> = ({ name, size = 'medium', children }) => {
const url = `https://wow.zamimg.com/images/wow/icons/${size}/${name}.jpg`
const className = `icon icon--${size}`
export const Icon: FC<Props> = ({ name, size = 'medium', golden = false, children }) => {
const className = classNames(
'icon',
`icon--${size}`, {
'icon--golden': golden
}
)
const bgSize = size === 'medium' ? 'large' : 'medium'
const bgStyle = {
backgroundImage: `url(https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg)`
}
return (
<div className={className} style={{ backgroundImage: `url(${url})`}}>
<div className={className}>
<div className="icon__bg" style={bgStyle} />
<div className="icon__frame" />
{children}
</div>
)
+15 -1
View File
@@ -3,6 +3,8 @@ import { Calculator } from './Calculator'
import { ClassPicker } from './ClassPicker'
import { match } from 'react-router-dom'
import { RouteComponentProps } from 'react-router'
import { decodeKnownTalents } from '../lib/tree'
import { classByName } from '../data/classes'
interface Props extends RouteComponentProps {
match: match<{
@@ -14,16 +16,28 @@ interface Props extends RouteComponentProps {
export class IndexRoute extends React.PureComponent<Props> {
static whyDidYouRender = true
componentDidMount() {
const { selectedClass } = this.props.match.params
if (selectedClass && !classByName[selectedClass]) {
this.props.history.replace('/')
}
}
render() {
const { match, history } = this.props
const { selectedClass, pointString } = match.params
if (selectedClass && !classByName[selectedClass]) {
return null
}
return (
<div className="index">
<ClassPicker selected={selectedClass} />
{selectedClass &&
<Calculator
initialTalents={pointString && decodeKnownTalents(pointString, selectedClass)}
selectedClass={selectedClass}
history={history}
/>
+83 -47
View File
@@ -1,66 +1,102 @@
$row-offset: 30px;
$row-distance: 70px;
@mixin rowStyle($rowNr) {
top: 30px + (($rowNr) * 70px);
}
$col-offset: 44px;
$col-distance: 56px;
$color-yellow: #ffd100;
$color-green: #1eff00;
$color-dark-green: #40bf40;
$color-subtle: #9d9d9d;
.talent {
position: absolute;
width: 40px;
height: 40px;
border: 1px solid black;
border-radius: 2px;
border-radius: 5px;
transition: filter .1s linear;
filter: none;
cursor: pointer;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
}
&--available {
.talent__status::after {
// background-color: rgba($color-green, .8);
box-shadow: inset 0px 0px 6px 3px rgba($color-green, .8);
}
small {
font-size: 8px;
line-height: 1em;
}
&--disabled {
filter: grayscale(100%)
}
&:not(&--disabled) {
cursor: pointer;
}
&--maxed {
}
@for $i from 0 through 6 {
&[data-row="#{$i}"] {
@include rowStyle($i)
.point-label {
color: $color-green;
}
}
&--maxed {
.talent__status::after {
box-shadow: inset 0px 0px 6px 3px rgba($color-yellow, .8);
}
$col-distance-offset: 44px;
$col-distance: 56px;
.point-label {
color: $color-yellow;
}
}
&[data-col="0"] { left: $col-distance-offset; }
&[data-col="1"] { left: $col-distance-offset + ($col-distance * 1); }
&[data-col="2"] { left: $col-distance-offset + ($col-distance * 2); }
&[data-col="3"] { left: $col-distance-offset + ($col-distance * 3); }
&--disabled {
filter: grayscale(100%);
&__points {
.talent__status {
opacity: .7;
}
}
// Rows
@for $i from 0 through 6 {
&[data-row="#{$i}"] {
top: $row-offset + (($i) * $row-distance);
}
}
// Columns
@for $i from 0 through 3 {
&[data-col="#{$i}"] {
left: $col-offset + ($col-distance * $i);
}
}
&__status {
position: absolute;
padding: 1px 2px;
bottom: -2px;
right: -2px;
width: 48px;
height: 46px;
bottom: -1px;
left: -4px;
background-image: url('/images/icons/large/default.png');
background-size: cover;
&:after {
content: '';
position: absolute;
width: 44px;
height: 44px;
top: 2px;
left: 2px;
border-radius: 5px;
}
}
}
.point-label {
position: absolute;
bottom: -5px;
right: -5px;
min-width: 7px;
text-align: center;
padding: 1px 3px;
color: #999;
font-size: 11px;
font-family: Arial, Helvetica, sans-serif;
background: #111;
border-radius: 4px;
user-select: none;
&--enabled {
color: white;
font-size: 12px;
background: black;
border-radius: 2px;
}
}
+15 -6
View File
@@ -37,9 +37,14 @@ export const TalentSlot: FC<Props> = (props) => {
const showPoints = points > 0 || availablePoints > 0
const disabled = props.disabled || !showPoints || !isAvailable(talent, specId, knownTalents)
const cn = classNames('talent', {
const containerClassNames = classNames('talent', {
'talent--disabled': !!disabled,
'talent--maxed': points >= talent.ranks.length
'talent--available': !disabled && points < talent.ranks.length,
'talent--maxed': points >= talent.ranks.length || (points > 0 && availablePoints === 0)
})
const pointsClassNames = classNames('point-label', {
'point-label--enabled': !disabled
})
const handleContextMenu = (e) => {
@@ -50,17 +55,21 @@ export const TalentSlot: FC<Props> = (props) => {
return (
<div
className={cn}
className={containerClassNames}
title={talent.ranks[0].toString()}
data-row={talent.row}
data-col={talent.col}
onClick={!disabled ? () => props.onClick(talent.id) : () => {}}
onContextMenu={handleContextMenu}
>
<Icon name={talent.icon} />
<div className="talent__status" />
<Icon name={talent.icon} size="medium" />
{showPoints &&
<div className="talent__points">{points}/{talent.ranks.length}</div>
{showPoints && !disabled &&
<div className={pointsClassNames}>
{points}
/{talent.ranks.length}
</div>
}
</div>
)