Got it working

This commit is contained in:
Joshua Seigler 2019-05-06 03:44:45 -04:00
parent 3b3e3c9c42
commit 1852bbf12e
49 changed files with 530 additions and 294 deletions

23
src/404.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Borderlands 3 skill calculator</title>
<script>
sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/'"></meta>
</head>
<body>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

View file

@ -1,26 +0,0 @@
[
{
"title": "Instant Prototyping",
"text": "Quickly scaffold new projects with your preferred view library and toolkit. Kick it off with a perfect Lighthouse score!"
},
{
"title": "Feature Rich",
"text": "Supports Babel, Bublé, Browserlist, TypeScript, PostCSS, ESLint, Prettier, and Service Workers out of the box!"
},
{
"title": "Fully Extensible",
"text": "Includes a plugin system that allows for easy, fine-grain control of your configuration... when needed."
},
{
"title": "Plug 'n Play",
"text": "Don't worry about configuration, unless you want to. Presets and plugins are automatically applied. Just install and go!"
},
{
"title": "Framework Agnostic",
"text": "Build with your preferred framework or with none at all! Official presets for Preact, React, Vue, and Svelte."
},
{
"title": "Static Site Generator",
"text": "Export your routes as \"pre-rendered\" HTML, which is great for SEO and works on any static hosting service."
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg>

Before

Width:  |  Height:  |  Size: 442 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12"><path fill="#ED412D" d="M6.5.1C3.4.1.8 2.8.8 6s2.6 5.9 5.7 5.9 5.7-2.7 5.7-5.9S9.7.1 6.5.1zm0 8.7C5 8.8 3.8 7.6 3.8 6S5 3.2 6.5 3.2 9.2 4.4 9.2 6 8 8.8 6.5 8.8z" /></svg>

Before

Width:  |  Height:  |  Size: 231 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#FDBD00" d="M10.3 4.3H7.7V1.7C7.7.8 7 0 6 0S4.3.8 4.3 1.7v2.5H1.7C.8 4.3 0 5 0 6s.8 1.7 1.7 1.7h2.5v2.5C4.3 11.2 5 12 6 12s1.7-.8 1.7-1.7V7.7h2.5c1 0 1.8-.7 1.8-1.7s-.8-1.7-1.7-1.7z" class="cross"/></svg>

Before

Width:  |  Height:  |  Size: 277 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 561.8 559.4"><path fill="#3E82F7" d="M383.4 559.4h-204l-2.6-.2c-51.3-4.4-94-37-108.8-83l-.2-.6L6 276.7l-.2-.5a119.4 119.4 0 0 1 43.7-131.4L212.1 23A115.7 115.7 0 0 1 351 23l163.5 122.5.4.3c39 30.3 56 82.6 42.2 130.3l-.3 1.1-61.5 198a116.1 116.1 0 0 1-111.9 84.2zm-197.9-120h195.2l61.1-196.8c0-.5-.3-1.6-.7-2.1L281.5 120.9 120.9 241.2l.2 1.2 60.8 195.8c.6.3 1.8.9 3.6 1.2zM441 240.3z"/></svg>

Before

Width:  |  Height:  |  Size: 445 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#8491A3" d="M6 7.5c-.9 0-1.5-.6-1.5-1.5S5.2 4.5 6 4.5c.9 0 1.5.7 1.5 1.5 0 .9-.6 1.5-1.5 1.5z"/></svg>

Before

Width:  |  Height:  |  Size: 175 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 14"><path fill="#2DA94F" stroke="#2DA94F" d="M5.9 1.2L.7 6.5c-.2.2-.2.5 0 .7l5.2 5.4c.2.2.5.2.7 0l5.2-5.4c.2-.2.2-.5 0-.7L6.6 1.2c-.2-.3-.5-.3-.7 0zM3.4 6.5L6 3.9c.2-.2.5-.2.7 0l2.6 2.6c.2.2.2.5 0 .7L6.6 9.9c-.2.2-.5.2-.7 0L3.4 7.3c-.2-.2-.2-.5 0-.8z" /></svg>

Before

Width:  |  Height:  |  Size: 317 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 247 KiB

View file

@ -1,4 +0,0 @@
main {
padding: 1rem 1rem 3rem;
margin: auto;
}

View file

@ -1,25 +1,18 @@
import { Router } from 'preact-router';
import Nav from '@components/Nav';
import Footer from '@components/Footer';
import Home from '@pages/Home';
import Operative from '@pages/Operative';
import Siren from '@pages/Siren';
// import style from './index.css';
import Beastmaster from '@pages/Beastmaster';
import BotJock from '@pages/BotJock';
const App = () =>
[
<Nav />,
<main>
<Router>
<Home path='/' />
<Operative path='/operative' />
<Siren path='/siren' />
</Router>
</main>,
<Footer />
];
(<Router>
<Home path='/' />
<Operative path='/operative' />
<Siren path='/siren' />
<Beastmaster path='/beastmaster' />
<BotJock path='/bot-jock' />
</Router>);
export default App;

View file

@ -31,7 +31,7 @@
transition: color var(--transition-duration);
}
.footer a:hover {
.footer a:hover, .footer a:focus {
color: #E2264D;
}

View file

@ -29,13 +29,18 @@
list-style: none;
}
.nav a {
display: inline-block;
.link {
color: inherit;
background-color: hsla(0, 0%, 0%, 0.25);
}
.link > a {
display: inline-block;
text-decoration: none;
padding: 0.5em;
}
.nav a:hover, .nav a:focus {
.current, .link:hover, .link:focus {
background-color: #FED600;
color: black;
}

View file

@ -1,15 +1,21 @@
import { Link } from 'preact-router';
import style from './index.css';
export default function Nav (props) {
export default function Nav ({ path }) {
const pages = [
{ name: 'Zane', path: '/operative' },
{ name: 'Amara', path: '/siren' },
{ name: 'FL4K', path: '/beastmaster' },
{ name: 'Moze', path: '/bot-jock' },
];
return (
<nav class={style.nav}>
<ul class={style.links}>
<li><Link href='/'>Home</Link></li>
<li><Link href='/operative'>Zane</Link></li>
<li><Link href='/siren'>Amara</Link></li>
<li><Link href='/beastmaster'>FL4K</Link></li>
<li><Link href='/bot-jock'>Moze</Link></li>
{ pages.map(page => {
return (<li class={`${style.link} ${path === page.path ? style.current : ''}`}>
<Link href={page.path}>{page.name}</Link>
</li>);
}) }
</ul>
</nav>
);

View file

@ -121,20 +121,31 @@
padding: 0.1em;
}
.skillTitle {
margin-bottom: 0.5rem;
font-weight: bold;
}
.description {
display: none;
z-index: 10;
text-align: left;
}
.effect {
margin-top: 0.5em;
}
.skill:hover .description {
pointer-events: none;
font-size: 0.8em;
background-color: hsla(0, 0%, 0%, 0.6);
background-color: hsl(0, 0%, 10%);
color: var(--whiteText);
border: 0.15rem solid hsla(0,0%,0%,0.8);
border: 0.15rem solid black;
padding: 0.5rem;
display: block;
position: absolute;
bottom: 110%;
width: 20rem;
transform: translateX(calc((var(--tree-index) - 1) * -50%));
transform: translateX(calc((1 - var(--treeindex)) * 40%));
}

View file

@ -21,7 +21,7 @@ export default function Skill ({
effect = rank => `Rank ${rank} effect`,
type = null,
enabled = true,
onChange = (oldValue, newValue) => console.log(`Skill from ${oldValue} to ${newValue}`),
onChange = (oldValue, newValue) => null,
}) {
const isAugment = [
SKILLS.AUGMENT_CHEVRON,
@ -60,7 +60,7 @@ export default function Skill ({
<div class={style.image}>{getInitials(name)}</div>
{ enabled && ranks > 0 && <div class={style.ranks}>{invested}/{ranks}</div>}
<div class={style.description}>
<h3>{name}</h3>
<h3 class={style.skillTitle}>{name}</h3>
{text}
<div class={style.effect}>
{effect(Math.max(invested, 1))}

View file

@ -0,0 +1,32 @@
export function setHash (skillsState) {
const hashparts = [];
for (let tree of Object.values(skillsState)) {
for (let tier of Object.values(tree)) {
for (let skill of Object.values(tier)) {
if (skill.type == null) {
hashparts.push(skill.invested || 0);
}
};
};
};
const url = window.location.href.split('#')[0] + '#' + hashparts.join('');
window.location.replace(url);
};
export function getHash (skillsState) {
const hash = window.location.href.split('#')[1] || '';
const hashparts = hash.match(/./g) || [];
const skills = JSON.parse(JSON.stringify(skillsState));
for (let tree of Object.keys(skills)) {
for (let tier of Object.keys(skills[tree])) {
for (let skill of Object.keys(skills[tree][tier])) {
if (skills[tree][tier][skill].type == null) {
skills[tree][tier][skill] = { invested: parseInt(hashparts.shift() || 0) };
} else {
skills[tree][tier][skill] = {};
}
};
};
};
return skills;
};

View file

@ -19,7 +19,7 @@
}
.subtitle {
text-transform: uppercase;
font-size: 0.5em;
font-size: 0.4em;
}
.trees, .tier {
@ -50,6 +50,7 @@
.skills {
position: relative;
user-select: none;
height: calc(3.5rem * 7)
}
.skills:before {
@ -84,3 +85,7 @@
.red {
--themeHue: 18;
}
a {
color: inherit;
}

View file

@ -1,99 +1,91 @@
import { useReducer } from 'preact/hooks';
import deepmerge from 'deepmerge';
// import { useReducer } from 'preact/hooks'; // Downgraded from Preact 10 for compat with @pwa/cli
import { Component } from 'preact';
import Skill from '@components/Skill';
import investmentValidator from './investmentValidator';
import Nav from '@components/Nav';
import Footer from '@components/Footer';
import { getHash } from './hashHandler';
import reducer from './reducer';
import style from './index.css';
function reducer (state, action) {
switch (action.type) {
case 'skillChange':
var newInvested = state.invested.slice(0);
newInvested[action.treeIndex] += action.newValue - action.oldValue;
var newSkills = deepmerge(state.skills, {
[action.treeName]: {
[action.tierIndex + '']: {
[action.skillName]: {
invested: action.newValue,
}
}
}
});
if (investmentValidator(newSkills)) {
return {
...state,
invested: newInvested,
skills: newSkills,
};
} else {
return state;
}
}
}
function contextKiller (event) {
event.preventDefault();
return false;
}
export default function VaultHunter ({
name = 'Unnamed',
discipline = 'Classless',
skills = {},
}) {
const initialState = {
invested: [0, 0, 0],
skills,
};
const [state, dispatch] = useReducer(reducer, initialState);
const trees =
Object.keys(state.skills).map((treeName, treeIndex) =>
<div
class={`${style.tree} ${[style.green, style.blue, style.red][treeIndex]}`}
style={{
'--invested': state.invested[treeIndex],
'--tree-index': treeIndex,
}}
>
<h2 class={style.treeName}>{ treeName }</h2>
<div class={style.skills}>
{ Object.keys(state.skills[treeName]).map((tier, tierIndex) =>
<div class={style.tier}>
{ Object.keys(state.skills[treeName][tier]).map(skillName =>
<Skill
{...state.skills[treeName][tier][skillName]}
name={skillName}
enabled={state.invested[treeIndex] >= 5 * tierIndex - 5}
onChange={skillChangeListenerFactory(skillName, treeIndex, treeName, tierIndex)}
/>
) }
</div>
) }
</div>
</div>
);
function skillChangeListenerFactory (skillName, treeIndex, treeName, tierIndex) {
return (oldValue, newValue) => {
dispatch({
type: 'skillChange',
skillName,
treeIndex,
treeName,
tierIndex,
newValue,
oldValue,
});
export default class VaultHunter extends Component {
constructor (props) {
super(props);
this.state = {
invested: [0, 0, 0],
skills: props.skills || {},
};
}
return (
<div class={style.VaultHunter} onContextMenu={contextKiller}>
<h1 class={style.title}>{ name }
<div class={style.subtitle}>the { discipline }</div>
</h1>
<div class={style.trees}>
{ trees }
componentDidMount () {
this.setState(reducer(this.state, {
type: 'loadSkills',
skills: getHash(this.state.skills),
}));
}
render ({
name = 'Unnamed',
discipline = 'Classless',
path
}) {
const skillChangeListenerFactory = (skillName, treeIndex, treeName, tierIndex) => {
return (oldValue, newValue) => {
this.setState(reducer(this.state, {
type: 'skillChange',
skillName,
treeIndex,
treeName,
tierIndex,
newValue,
oldValue,
}));
};
};
const trees =
Object.keys(this.state.skills).map((treeName, treeIndex) => {
return (
<div class={`${style.tree} ${[style.green, style.blue, style.red][treeIndex]}`}>
<style>{`.${style.tree}:nth-child(${treeIndex + 1}) { --invested: ${this.state.invested[treeIndex]}; --treeindex: ${treeIndex};}`}</style>
<h2 class={style.treeName}>{ treeName }</h2>
<div class={style.skills}>
{ Object.keys(this.state.skills[treeName]).map((tier, tierIndex) =>
<div class={style.tier}>
{ Object.keys(this.state.skills[treeName][tier]).map(skillName =>
<Skill
{...this.state.skills[treeName][tier][skillName]}
name={skillName}
enabled={this.state.invested[treeIndex] >= 5 * tierIndex - 5}
onChange={skillChangeListenerFactory(skillName, treeIndex, treeName, tierIndex)}
/>
) }
</div>
) }
</div>
</div>
);
});
return (
<div>
<Nav path={path} />
<main>
<div class={style.VaultHunter} onContextMenu={contextKiller}>
<h1 class={style.title}>{ name }
<div class={style.subtitle}>the { discipline }</div>
</h1>
<div class={style.trees}>
{ trees }
</div>
</div>
</main>
<Footer />
</div>
</div>
);
);
}
}

View file

@ -1,11 +1,7 @@
export default function investmentValidator (skills) {
// ok &= (
// (level == thisLevel && total == 0 && treeTotal >= invested + total) ||
// (level != thisLevel && total == 0) ||
// (total > 0 && (level * 5 <= invested))
// );
console.log('----------------------');
let totalSpent = 0;
let treeTotals = [0, 0, 0];
let treeIndex = 0;
for (let tree of Object.values(skills)) {
let treeTotal = 0;
let tierIndex = 0;
@ -15,13 +11,14 @@ export default function investmentValidator (skills) {
if (skill.invested < 0 || skill.invested > skill.ranks) { return false; }
tierTotal += skill.invested || 0;
};
// console.log(`${tierTotal} > 0 && ${treeTotal} + 5 < ${tierIndex} * 5 = ${!(tierTotal > 0 && treeTotal + 5 < tierIndex * 5)}`);
if (tierTotal > 0 && treeTotal + 5 < tierIndex * 5) { return false; }
treeTotal += tierTotal;
tierIndex += 1;
};
treeTotals[treeIndex] = treeTotal;
totalSpent += treeTotal;
treeIndex += 1;
};
if (totalSpent > 47) { return false; }
return true;
if (totalSpent > 48) { return false; }
return treeTotals;
}

View file

@ -0,0 +1,40 @@
import deepmerge from 'deepmerge';
import investmentValidator from './investmentValidator';
import { setHash } from './hashHandler';
export default function reducer (state, action) {
switch (action.type) {
case 'skillChange':
var newSkills = deepmerge(state.skills, {
[action.treeName]: {
[action.tierIndex + '']: {
[action.skillName]: {
invested: action.newValue,
}
}
}
});
const skillChangeTotals = investmentValidator(newSkills);
if (skillChangeTotals) {
setHash(newSkills);
return {
...state,
invested: skillChangeTotals,
skills: newSkills,
};
} else {
return state;
}
case 'loadSkills':
const loadSkillsTotals = investmentValidator(action.skills);
if (loadSkillsTotals) {
return {
...state,
invested: loadSkillsTotals,
skills: deepmerge(state.skills, action.skills),
};
} else {
return state;
}
}
}

View file

@ -27,29 +27,22 @@
body {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
color: #F8F8FA;
font-family: var(--font-list);
-webkit-font-smoothing: antialiased;
background-color: #333;
background-image: url('@assets/backgrounds/background2.jpg');
background-image: url('@assets/backgrounds/background.jpg');
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
color: #E2264D;
text-decoration: underline;
}
a:visited {
color: inherit;
body > * {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
}

View file

@ -0,0 +1,3 @@
VaultHunter {
opacity: 0.5;
}

View file

@ -0,0 +1,14 @@
import VaultHunter from '@components/VaultHunter';
import skills from './skills.js';
export default function Beastmaster ({ path }) {
return (
<VaultHunter
name='FL4K'
discipline='Beastmaster'
skills={skills}
path={path}
/>
);
}

View file

@ -0,0 +1,34 @@
import SKILLS from '@constants/skills';
const skills = {
"Tree One": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
ranks: 0,
effect: rank => `?`,
},
},
},
"Tree Two": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
effect: rank => `?`,
},
},
},
"Tree Three": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
effect: rank => `?`,
},
},
},
};
export default skills;

View file

@ -0,0 +1,3 @@
VaultHunter {
opacity: 0.5;
}

View file

@ -0,0 +1,14 @@
import VaultHunter from '@components/VaultHunter';
import skills from './skills.js';
export default function BotJock ({ path }) {
return (
<VaultHunter
name='Moze'
discipline='Bot Jock'
skills={skills}
path={path}
/>
);
}

View file

@ -0,0 +1,34 @@
import SKILLS from '@constants/skills';
const skills = {
"Tree One": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
ranks: 0,
effect: rank => `?`,
},
},
},
"Tree Two": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
effect: rank => `?`,
},
},
},
"Tree Three": {
"0": {
"?": {
text: "?",
type: SKILLS.ACTION_SKILL,
effect: rank => `?`,
},
},
},
};
export default skills;

View file

@ -0,0 +1,34 @@
.splash {
margin: auto;
}
.wrapper {
text-align: center;
padding: 1rem;
background-color: hsla(0, 0%, 0%, 0.5);
}
.link {
display: inline-block;
margin: 0.5rem;
background-color: black;
color: hsl(51, 100%, 50%);
padding: 0.25em;
text-align: center;
font-weight: bold;
font-size: 3rem;
text-decoration: none;
}
.link:hover, .link:focus {
color: black;
background-color: hsl(51, 100%, 50%);
}
.name, .job {
text-transform: uppercase;
}
.job {
font-size: 0.4em;
}

View file

@ -1,9 +1,34 @@
// import style from './index.css';
import { Link } from 'preact-router';
import Footer from '@components/Footer';
import style from './index.css';
export default function () {
const pages = [
{ name: 'Zane', job: 'the Operative', path: '/operative' },
{ name: 'Amara', job: 'the Siren', path: '/siren' },
{ name: 'FL4K', job: 'the Beastmaster', path: '/beastmaster' },
{ name: 'Moze', job: 'the Bot Jock', path: '/bot-jock' },
];
return (
<div>
Home
<div class={style.splash}>
<div class={style.wrapper}>
<h1>Borderlands 3 skill calculator</h1>
{ pages.map(page => {
return (
<Link class={style.link} href={page.path}>
<div class={style.name}>
{page.name}
<div class={style.job}>
{page.job}
</div>
</div>
</Link>
);
}) }
</div>
</div>
<Footer />
</div>
);
}

View file

@ -2,12 +2,13 @@ import VaultHunter from '@components/VaultHunter';
import skills from './skills.js';
export default function Operative () {
export default function Operative ({ path }) {
return (
<VaultHunter
name='Zane'
discipline='Operative'
skills={skills}
path={path}
/>
);
}

View file

@ -2,12 +2,13 @@ import VaultHunter from '@components/VaultHunter';
import skills from './skills.js';
export default function Siren () {
export default function Siren ({ path }) {
return (
<VaultHunter
name='Amara'
discipline='Siren'
skills={skills}
path={path}
/>
);
}