diff --git a/TODO.md b/TODO.md index eaa4da8..2663040 100644 --- a/TODO.md +++ b/TODO.md @@ -3,9 +3,9 @@ - [ ] General: Add redux - [ ] General: Talent tooltips - [ ] General: Responsive on mobile -- [ ] Talent tree: Arrows for dependencies (all done except for right-down arrow) - [ ] Talent tree: Reset button per tree (?) - [ ] Fix: Initial load `pointString` validation (make sure all talents are valid and their deps are met) +- [x] Talent tree: Arrows for dependencies - [x] System: Generate URL for chosen talents - [x] Talent tree: Prettier talent frames - [x] Talent tree: Colour markings on icons diff --git a/src/components/Arrow.scss b/src/components/Arrow.scss index 557ad49..a19ffc1 100644 --- a/src/components/Arrow.scss +++ b/src/components/Arrow.scss @@ -13,7 +13,39 @@ } @function calcArrowHeight($length) { - @return 2px + ($row-offset * $length) + ($icon-size * ($length - 1)) + @return 2px + ($row-offset * $length) + ($icon-size * ($length - 1)); +} + +@mixin arrow-left { + background-image: url('../images/arrows/left.png'); + background-position: center left; + + &.arrow--active { + background-image: url('../images/arrows/left-active.png'); + } + + // Cols + @for $i from 0 through 3 { + &[data-col="#{$i}"] { + left: -3px + calcLeftOffset($i); + } + } +} + +@mixin arrow-right { + background-image: url('../images/arrows/right.png'); + background-position: center right; + + &.arrow--active { + background-image: url('../images/arrows/right-active.png'); + } + + // Cols + @for $i from 0 through 3 { + &[data-col="#{$i}"] { + left: 3px + calcRightOffset($i); + } + } } .arrow { @@ -40,35 +72,11 @@ } &--right { - background-image: url('../images/arrows/right.png'); - background-position: center right; - - &.arrow--active { - background-image: url('../images/arrows/right-active.png'); - } - - // Cols - @for $i from 0 through 3 { - &[data-col="#{$i}"] { - left: 3px + calcRightOffset($i); - } - } + @include arrow-right() } &--left { - background-image: url('../images/arrows/left.png'); - background-position: center left; - - &.arrow--active { - background-image: url('../images/arrows/left-active.png'); - } - - // Cols - @for $i from 0 through 3 { - &[data-col="#{$i}"] { - left: -3px + calcLeftOffset($i); - } - } + @include arrow-left() } &--down { @@ -83,7 +91,7 @@ // Rows @for $i from 0 through 6 { &[data-row="#{$i}"] { - top: 40px + baseRowTopOffset($i); + top: $icon-size + baseRowTopOffset($i); } } @@ -102,23 +110,103 @@ } } - &--right-down { - // Horizontal - ::before { - content: ""; - position: absolute; - height: $arrow-width; - background-image: url('../images/arrows/rightdown.png'); - background-position: center right; + &--right-down, + &--left-down { + // Position based on row + @for $i from 0 through 6 { + &[data-row="#{$i}"] { + top: 12px + baseRowTopOffset($i); + + &:after { + top: $arrow-width; + } + } } - // Vertical - ::after { + // Width + @for $i from 0 through 3 { + &[data-width="#{$i}"] { + width: 2px + ($icon-size * $i); + } + } + + // Height + @for $i from 0 through 3 { + &[data-height="#{$i}"] { + height: 8px + ($row-offset + ($icon-size * .5)) * $i; + } + } + + // Horizontal + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: $arrow-width; + } + + // Arrow down + &:after { content: ""; position: absolute; width: $arrow-width; + top: $arrow-width; + right: 0; + bottom: -3px; background-image: url('../images/arrows/down.png'); background-position: center bottom; } } + + &--right-down { + // Positioning based on cols + @for $i from 0 through 3 { + &[data-col="#{$i}"] { + left: 2px + calcRightOffset($i); + } + } + + &.arrow--active { + &:before { background-image: url('../images/arrows/rightdown-active.png'); } + &:after { background-image: url('../images/arrows/down-active.png'); } + } + + // Arrow to the right + &:before { + background-image: url('../images/arrows/rightdown.png'); + background-position: center right; + } + + // Arrow down + &:after { + right: 0; + } + } + + &--left-down { + // Positioning based on cols + @for $i from 0 through 3 { + &[data-col="#{$i}"] { + left: -2px + calcLeftOffset($i); + } + } + + &.arrow--active { + &:before { background-image: url('../images/arrows/leftdown-active.png'); } + &:after { background-image: url('../images/arrows/down-active.png'); } + } + + // Arrow to the left + &:before { + background-image: url('../images/arrows/leftdown.png'); + background-position: center left; + } + + // Arrow down + &:after { + left: 0; + } + } } \ No newline at end of file diff --git a/src/components/Arrow.tsx b/src/components/Arrow.tsx index 3795df1..926b70d 100644 --- a/src/components/Arrow.tsx +++ b/src/components/Arrow.tsx @@ -8,23 +8,40 @@ interface Props { active?: boolean } -export const Arrow: FC = ({ from, to, active = false }) => { - const length = to.row === from.row - ? Math.abs(to.col - from.col) - : to.row - from.row +const getDirection = (from: TalentData, to: TalentData): string => { + if (to.row > from.row && to.col === from.col) { + return 'down' + } else if (to.row === from.row && to.col > from.col) { + return 'right' + } else if (to.row === from.row && to.col < from.col) { + return 'left' + } else if (to.row > from.row && to.col === from.col + 1) { + return 'right-down' + } else if (to.row > from.row && to.col === from.col - 1) { + return 'left-down' + } + throw new Error(`Could not determine direction from (row ${from.row}, col ${from.col}) to (row ${to.row}, col ${to.col})`) +} +export const Arrow: FC = ({ from, to, active = false }) => { const props = { 'data-col': from.col, 'data-row': from.row, - 'data-length': length, } - const className = classNames('arrow', { + const height = to.row - from.row + const width = Math.abs(to.col - from.col) + + const dir = getDirection(from, to) + if (dir === 'right-down' || dir === 'left-down') { + props['data-height'] = height + props['data-width'] = width + } else { + props['data-length'] = to.row === from.row ? width : height + } + + const className = classNames('arrow', `arrow--${dir}`, { 'arrow--active': active, - 'arrow--down': to.row > from.row, - 'arrow--right': to.row === from.row && to.col > from.col, - 'arrow--left': to.row === from.row && to.col < from.col, - 'arrow--right-down': to.row === from.row + 1 && to.col === from.col + 1 }) return
diff --git a/src/components/Icon.scss b/src/components/Icon.scss index 0684385..fbd99c8 100644 --- a/src/components/Icon.scss +++ b/src/components/Icon.scss @@ -38,10 +38,18 @@ } } + &--loading { + .icon__bg { + opacity: 0; + } + } + &__bg { position: absolute; background-size: cover; border-radius: 5px; + opacity: 1; + transition: all 100ms ease-out; } &__frame { diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index d5b6986..445a3e4 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import React, { FC, useState, useEffect } from 'react' import classNames from 'classnames' import './Icon.scss' @@ -9,21 +9,25 @@ interface Props { } export const Icon: FC = ({ name, size = 'medium', golden = false, children }) => { - const className = classNames( - 'icon', - `icon--${size}`, { - 'icon--golden': golden - } - ) + const [hasLoadedImage, setHasLoadedImage] = useState(false) const bgSize = size === 'medium' ? 'large' : 'medium' - const bgStyle = { - backgroundImage: `url(https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg)` - } + const url = `https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg` + + useEffect(() => { + const img = new Image() + img.onload = () => setHasLoadedImage(true) + img.src = url + }, []) + + const className = classNames('icon', `icon--${size}`, { + 'icon--golden': golden, + 'icon--loading': !hasLoadedImage + }) return (
-
+
{children}
diff --git a/src/components/TalentTree.tsx b/src/components/TalentTree.tsx index 0336d55..88bbf25 100644 --- a/src/components/TalentTree.tsx +++ b/src/components/TalentTree.tsx @@ -14,6 +14,7 @@ interface Props { export const TalentTree: React.FC = ({ specId, knownTalents, availablePoints, onTalentPress }) => { const talents = Object.values(talentsBySpec[specId]) + const bgImg = require(`../images/specs/${specId}.jpg`) const handleClick = useCallback( (talentId) => onTalentPress(specId, talentId, 1), @@ -24,12 +25,8 @@ export const TalentTree: React.FC = ({ specId, knownTalents, availablePoi [specId, onTalentPress] ) - const bodyStyle = { - backgroundImage: `url(${require(`../images/specs/${specId}.jpg`)})` - } - const arrows = talents - .filter((talent) => talent.requires.length > 0) + .filter((talent) => talent.requires.length) .map((talent) => { return = ({ specId, knownTalents, availablePoi

{specNames[specId]} ({getPointsInSpec(specId, knownTalents)})

-
+
{talents.map((talent) =>