Add /playground and initial tooltips
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
- [ ] General: Responsive on mobile
|
- [ ] General: Responsive on mobile
|
||||||
- [ ] Talent tree: Reset button per tree (?)
|
- [ ] Talent tree: Reset button per tree (?)
|
||||||
- [ ] 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: Arrow should start underneath the icon
|
||||||
- [x] Talent tree: Arrows for dependencies
|
- [x] Talent tree: Arrows for dependencies
|
||||||
- [x] System: Generate URL for chosen talents
|
- [x] System: Generate URL for chosen talents
|
||||||
- [x] Talent tree: Prettier talent frames
|
- [x] Talent tree: Prettier talent frames
|
||||||
|
|||||||
+29
-3
@@ -1,10 +1,33 @@
|
|||||||
@import "sass/config";
|
@import "sass/config";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
color: white;
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
font-family: Verdana;
|
font-family: Verdana;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: pink;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: lighten(pink, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-items {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.calculator {
|
.calculator {
|
||||||
&__points {
|
&__points {
|
||||||
color: white;
|
color: white;
|
||||||
@@ -48,10 +71,13 @@ body {
|
|||||||
|
|
||||||
.class-picker {
|
.class-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-top: 2em;
|
padding: 0;
|
||||||
margin-bottom: 2em;
|
margin: 2em 0;
|
||||||
|
|
||||||
|
&--center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
&__class {
|
&__class {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
|||||||
+6
-2
@@ -1,14 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import './App.scss'
|
import './App.scss'
|
||||||
import { IndexRoute } from './components/IndexRoute'
|
import { IndexRoute } from './components/IndexRoute'
|
||||||
import { BrowserRouter as Router, Route } from 'react-router-dom'
|
import { PlaygroundRoute } from './components/PlaygroundRoute'
|
||||||
|
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Router basename={process.env.NODE_ENV === 'production' ? '/wow-talent-calculator' : ''}>
|
<Router basename={process.env.NODE_ENV === 'production' ? '/wow-talent-calculator' : ''}>
|
||||||
{/* <Router basename={process.env.NODE_ENV !== 'development' ? '%PUBLIC_URL%' : ''}> */}
|
{/* <Router basename={process.env.NODE_ENV !== 'development' ? '%PUBLIC_URL%' : ''}> */}
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Route path="/:selectedClass?/:pointString?" component={IndexRoute} />
|
<Switch>
|
||||||
|
<Route exact path="/playground" component={PlaygroundRoute} />
|
||||||
|
<Route path="/:selectedClass?/:pointString?" component={IndexRoute} />
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,8 +110,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--right-down,
|
&--side-down {
|
||||||
&--left-down {
|
|
||||||
// Position based on row
|
// Position based on row
|
||||||
@for $i from 0 through 6 {
|
@for $i from 0 through 6 {
|
||||||
&[data-row="#{$i}"] {
|
&[data-row="#{$i}"] {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const Arrow: FC<Props> = ({ from, to, active = false }) => {
|
|||||||
|
|
||||||
const height = to.row - from.row
|
const height = to.row - from.row
|
||||||
const width = Math.abs(to.col - from.col)
|
const width = Math.abs(to.col - from.col)
|
||||||
|
|
||||||
const dir = getDirection(from, to)
|
const dir = getDirection(from, to)
|
||||||
if (dir === 'right-down' || dir === 'left-down') {
|
if (dir === 'right-down' || dir === 'left-down') {
|
||||||
props['data-height'] = height
|
props['data-height'] = height
|
||||||
@@ -42,6 +42,7 @@ export const Arrow: FC<Props> = ({ from, to, active = false }) => {
|
|||||||
|
|
||||||
const className = classNames('arrow', `arrow--${dir}`, {
|
const className = classNames('arrow', `arrow--${dir}`, {
|
||||||
'arrow--active': active,
|
'arrow--active': active,
|
||||||
|
'arrow--side-down': dir === 'right-down' || dir === 'left-down'
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div className={className} {...props} />
|
return <div className={className} {...props} />
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ export class Calculator extends React.PureComponent<Props> {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="/shaman/-5505000055523051-55">Shaman test</a></li>
|
<li><a href="/shaman/-5505000055523051-55">Shaman test</a></li>
|
||||||
<li><a href="/shaman/-5595000055523051-55">Shaman test broken</a></li>
|
<li><a href="/shaman/-5595000055523051-55">Shaman test broken</a></li>
|
||||||
<li><a href="/rogue/-005055-50205302332212051">Rogue (should break, does not meet requirement)</a></li>
|
<li><a href="/rogue/325323125551351-3253552122555155231-55225313333212151">Full Rogue</a></li>
|
||||||
<li><a href="/rogue/-005055-50205302333212041">Rogue can unlearn first row AND dependency</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import classNames from 'classnames'
|
|||||||
interface Props {
|
interface Props {
|
||||||
/** Name of the selected class, lowercase */
|
/** Name of the selected class, lowercase */
|
||||||
selected?: string
|
selected?: string
|
||||||
|
center?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const classNameForItem = (c: ClassData, selected: string) => classNames('class-picker__class', {
|
const classNameForItem = (c: ClassData, selected: string) => classNames('class-picker__class', {
|
||||||
@@ -18,10 +19,11 @@ export class ClassPicker extends React.PureComponent<Props> {
|
|||||||
static whyDidYouRender = true
|
static whyDidYouRender = true
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selected } = this.props
|
const { selected, center = false } = this.props
|
||||||
|
|
||||||
const cn = classNames('class-picker', {
|
const cn = classNames('class-picker', {
|
||||||
'class-picker--has-selection': !!selected
|
'class-picker--has-selection': !!selected,
|
||||||
|
'class-picker--center': center,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+32
-15
@@ -1,11 +1,34 @@
|
|||||||
@import "../sass/_config";
|
@import "../sass/_config";
|
||||||
|
|
||||||
|
@mixin icon-size($size) {
|
||||||
|
$offset: $size * 0.1;
|
||||||
|
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
|
||||||
|
.icon__bg {
|
||||||
|
width: ceil($size - $offset);
|
||||||
|
height: ceil($size - $offset);
|
||||||
|
border-radius: $size / 8;
|
||||||
|
top: $size * 0.05;
|
||||||
|
left: $size * 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon__frame {
|
||||||
|
width: $size + $offset;
|
||||||
|
height: $size + $offset;
|
||||||
|
top: -($offset * .5);
|
||||||
|
left: -($offset * .5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.icon__bg {
|
.icon__bg {
|
||||||
@@ -19,17 +42,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--medium {
|
&--small {
|
||||||
width: 40px;
|
@include icon-size(20px);
|
||||||
height: 40px;
|
}
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
.icon__bg {
|
&--medium {
|
||||||
width: 36px;
|
@include icon-size(40px);
|
||||||
height: 36px;
|
}
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
&--large {
|
||||||
}
|
@include icon-size(60px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--golden {
|
&--golden {
|
||||||
@@ -47,17 +69,12 @@
|
|||||||
&__bg {
|
&__bg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 5px;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: all 100ms ease-out;
|
transition: all 100ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__frame {
|
&__frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
top: -2px;
|
|
||||||
left: -2px;
|
|
||||||
background-image: url('../images/icons/large/default.png');
|
background-image: url('../images/icons/large/default.png');
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
export const Icon: FC<Props> = ({ name, size = 'medium', golden = false, children }) => {
|
export const Icon: FC<Props> = ({ name, size = 'medium', golden = false, children }) => {
|
||||||
const [hasLoadedImage, setHasLoadedImage] = useState(false)
|
const [hasLoadedImage, setHasLoadedImage] = useState(false)
|
||||||
|
|
||||||
const bgSize = size === 'medium' ? 'large' : 'medium'
|
const bgSize = size !== 'small' ? 'large' : 'medium'
|
||||||
const url = `https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg`
|
const url = `https://wow.zamimg.com/images/wow/icons/${bgSize}/${name}.jpg`
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class IndexRoute extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="index">
|
<div className="index">
|
||||||
<ClassPicker selected={selectedClass} />
|
<ClassPicker center selected={selectedClass} />
|
||||||
|
|
||||||
{selectedClass &&
|
{selectedClass &&
|
||||||
<Calculator
|
<Calculator
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ClassPicker } from './ClassPicker'
|
||||||
|
import { match } from 'react-router-dom'
|
||||||
|
import { RouteComponentProps } from 'react-router'
|
||||||
|
import { Icon } from './Icon';
|
||||||
|
import { Tooltip } from './Tooltip';
|
||||||
|
|
||||||
|
interface Props extends RouteComponentProps {
|
||||||
|
match: match<{
|
||||||
|
selectedClass: string
|
||||||
|
pointString: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconNames = [
|
||||||
|
'foo',
|
||||||
|
'spell_holy_prayerofhealing',
|
||||||
|
'ability_sap',
|
||||||
|
'class_shaman',
|
||||||
|
'inv_ammo_firetar',
|
||||||
|
'spell_shadow_requiem',
|
||||||
|
]
|
||||||
|
|
||||||
|
export class PlaygroundRoute extends React.PureComponent<Props> {
|
||||||
|
static whyDidYouRender = true
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { match, history } = this.props
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="playground container">
|
||||||
|
<h2>Class Picker</h2>
|
||||||
|
<ClassPicker />
|
||||||
|
|
||||||
|
<h2>Icons</h2>
|
||||||
|
|
||||||
|
<h3>Small Icons</h3>
|
||||||
|
<div className="inline-items">
|
||||||
|
{iconNames.map((n) => <Icon key={n} name={n} size="small" />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Medium Icons</h3>
|
||||||
|
<div className="inline-items">
|
||||||
|
{iconNames.map((n) => <Icon key={n} name={n} size="medium" />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Large Icons</h3>
|
||||||
|
<div className="inline-items">
|
||||||
|
{iconNames.map((n) => <Icon key={n} name={n} size="large" />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Tooltip</h2>
|
||||||
|
<Tooltip inline />
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<strong>I can use normal HTML in here</strong>
|
||||||
|
<br />
|
||||||
|
And even <a href="#">link</a> to exciting places!
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="Sanctuary Post" />
|
||||||
|
|
||||||
|
<Tooltip title="Fixed width" fixed />
|
||||||
|
|
||||||
|
<Tooltip title="Override fixed width" fixed width="600px" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
@mixin tooltip-base {
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
&__inner {
|
||||||
|
display: inline-block;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__top {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
padding: 3px;
|
||||||
|
background: url('../images/tooltip-background.png');
|
||||||
|
background-position: top right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
min-height: 2px;
|
||||||
|
padding: 8px 4px 2px 10px;
|
||||||
|
background: url('../images/tooltip-background.png');
|
||||||
|
background-position: top left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
padding: 3px;
|
||||||
|
background: url('../images/tooltip-background.png');
|
||||||
|
background-position: bottom left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
padding: 3px;
|
||||||
|
background: url('../images/tooltip-background.png');
|
||||||
|
background-position: bottom right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
@include tooltip-base;
|
||||||
|
|
||||||
|
&--inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--fixed {
|
||||||
|
width: 320px;
|
||||||
|
|
||||||
|
.tooltip__inner {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip__body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,39 @@
|
|||||||
import './Tooltip.scss'
|
import './Tooltip.scss'
|
||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
title?: string
|
||||||
|
/** Override width of tooltip. Needs `fixed` to be true to have effect. */
|
||||||
|
width?: string
|
||||||
|
/** Fixed width */
|
||||||
|
fixed?: boolean
|
||||||
|
/** Display tooltip inline */
|
||||||
|
inline?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tooltip: FC<Props> = (props) => {
|
export const Tooltip: FC<Props> = (props) => {
|
||||||
return <div className="tooltip">
|
const { title, children } = props
|
||||||
|
|
||||||
|
const cn = classNames('tooltip', {
|
||||||
|
'tooltip--fixed': props.fixed,
|
||||||
|
'tooltip--inline': props.inline,
|
||||||
|
})
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
width: props.width
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cn} style={style}>
|
||||||
|
<div className="tooltip__inner">
|
||||||
|
<div className="tooltip__top">
|
||||||
|
<div className="tooltip__body">
|
||||||
|
{title}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tooltip__footer" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
+12
-1
@@ -11,4 +11,15 @@ $color-yellow: #ffd100;
|
|||||||
$color-green: #1eff00;
|
$color-green: #1eff00;
|
||||||
$color-dark-green: #40bf40;
|
$color-dark-green: #40bf40;
|
||||||
$color-subtle: #9d9d9d;
|
$color-subtle: #9d9d9d;
|
||||||
$color-icon-overlay: #6396d6;
|
$color-icon-overlay: #6396d6;
|
||||||
|
|
||||||
|
// Class colours
|
||||||
|
$color-warrior: #c69b6d;
|
||||||
|
$color-paladin: #f48cba;
|
||||||
|
$color-hunter: #aad372;
|
||||||
|
$color-rogue: #fff468;
|
||||||
|
$color-priest: #fff;
|
||||||
|
$color-shaman: #2359ff;
|
||||||
|
$color-mage: #68ccef;
|
||||||
|
$color-warlock: #9382c9;
|
||||||
|
$color-druid: #ff7c0a;
|
||||||
|
|||||||
Reference in New Issue
Block a user