Refactored WeaponGrid

Fetching the party now happens in the `Party` route

`WeaponGridMainhand` and `WeaponGridUnit` were consolidated into `WeaponUnit`

Logged-in users can update grids that they created
This commit is contained in:
Justin Edmund 2020-09-25 03:43:30 -07:00
parent 46d32ba2a6
commit bbd329532b
7 changed files with 309 additions and 353 deletions

View file

@ -1,112 +1,77 @@
import React, { useEffect, useState } from 'react'
import { withRouter } from 'react-router'
import api from '~utils/api'
import WeaponGridMainhand from '~components/WeaponGridMainhand/WeaponGridMainhand'
import WeaponGridUnit from '~components/WeaponGridUnit/WeaponGridUnit'
import WeaponUnit from '~components/WeaponUnit/WeaponUnit'
import Button from '~components/Button/Button'
import './WeaponGrid.css'
interface GridWeapon {
id: string
mainhand: boolean
position: number | null
weapon: Weapon
}
interface Props {
shortcode: string
userId: string
partyId: string
mainhand: Weapon | undefined
grid: GridArray
editable: boolean
exists: boolean
found: boolean
}
type GridArray = { [key: number]: Weapon }
type GridArray = { [key: number]: Weapon }
const WeaponGrid = (props: Props) => {
const [partyId, setPartyId] = useState<string>()
const [shortcode, setShortcode] = useState<string>()
const numWeapons: number = 9
const [mainhand, setMainhand] = useState<Weapon>()
const [weapons, setWeapons] = useState<GridArray>({})
const numWeapons: number = 9
useEffect(() => {
if (props.shortcode) {
fetchGrid(props.shortcode)
} else {
setIsValid(true)
}
}, [])
if (props.exists && props.found)
configure()
}, [props.mainhand, props.grid])
function fetchGrid(shortcode: string) {
return api.endpoints.parties.getOne({ id: shortcode })
.then(response => {
setupGrid(response)
})
.catch(error => {
if (error.response.status == 404) {
gridNotFound()
}
})
}
function receiveMainhand(weapon: Weapon, _: number) {
// Store the mainhand weapon
setMainhand(weapon)
if (partyId === undefined) {
let _partyId = ''
createParty().then(response => {
const party = response.data.party
setPartyId(party.id)
_partyId = party.id
return party.shortcode
})
.then((code: string) => {
setShortcode(shortcode)
window.history.replaceState(null, `Grid Tool: ${code}`, `/p/${code}`)
})
.then(() => {
saveMainhand(_partyId, weapon)
})
} else {
saveMainhand(partyId, weapon)
}
}
function receiveWeapon(weapon: Weapon, position: number) {
// Store the grid unit weapon at the correct position
let newWeapons = Object.assign({}, weapons)
newWeapons[position] = weapon
setWeapons(newWeapons)
if (partyId === undefined) {
let _partyId = ''
createParty().then(response => {
const party = response.data.party
setPartyId(party.id)
_partyId = party.id
return party.shortcode
})
.then((code: string) => {
setShortcode(shortcode)
window.history.replaceState(null, `Grid Tool: ${code}`, `/p/${code}`)
})
.then(() => {
saveWeapon(_partyId, weapon, position)
})
} else {
saveWeapon(partyId, weapon, position)
}
function configure() {
setMainhand(props.mainhand)
setWeapons(props.grid)
}
function createParty() {
return api.endpoints.parties.create({})
const body = (props.userId === '') ? {} : {
party: {
user_id: props.userId
}
}
return api.endpoints.parties.create(body)
}
function receiveWeapon(weapon: Weapon, position: number) {
const isMainhand = position == -1
if (isMainhand) {
setMainhand(weapon)
} else {
// Store the grid unit weapon at the correct position
let newWeapons = Object.assign({}, weapons)
newWeapons[position] = weapon
setWeapons(newWeapons)
}
if (props.partyId == undefined) {
createParty()
.then(response => {
return response.data.party
})
.then(party => {
window.history.replaceState(null, `Grid Tool`, `/p/${party.shortcode}`)
return party.id
})
.then(partyId => {
saveWeapon(partyId, weapon, position)
})
} else {
saveWeapon(props.partyId, weapon, position)
}
}
function saveWeapon(pid: string, weapon: Weapon, position: number) {
@ -115,50 +80,56 @@ const WeaponGrid = (props: Props) => {
'party_id': pid,
'weapon_id': weapon.id,
'position': position,
'mainhand': false
'mainhand': (position == -1)
}
}
api.endpoints.weapons.create(body)
}
function saveMainhand(pid: string, weapon: Weapon) {
const body = {
'weapon': {
'party_id': pid,
'weapon_id': weapon.id,
'mainhand': true
}
}
api.endpoints.weapons.create(body)
function renderGrid() {
return (
<div className="WeaponGrid">
<WeaponUnit
editable={props.editable}
key="grid_mainhand"
onReceiveData={receiveWeapon}
position={-1}
unitType={0}
weapon={mainhand}
/>
<ul id="grid_weapons">
{
Array.from(Array(numWeapons)).map((x, i) => {
return (
<li key={`grid_unit_${i}`} >
<WeaponUnit
editable={props.editable}
onReceiveData={receiveWeapon}
position={i}
unitType={1}
weapon={weapons[i]}
/>
</li>
)
})
}
</ul>
</div>
)
}
return (
<div className="WeaponGrid">
<WeaponGridMainhand
editable={props.editable}
key="grid_mainhand"
onReceiveData={receiveMainhand}
position={0}
weapon={mainhand}
/>
function renderGridNotFound() {
return (
<div id="NotFound">
<h2>There's no grid here.</h2>
<Button type="new">New grid</Button>
</div>
)
}
<ul id="grid_weapons">
{
Array.from(Array(numWeapons)).map((x, i) => {
return <WeaponGridUnit
editable={props.editable}
key={`grid_unit_${i}`}
onReceiveData={receiveWeapon}
position={i}
weapon={weapons[i]}
/>
})
}
</ul>
</div>
)
return (props.found) ? renderGrid() : renderGridNotFound()
}
export default withRouter(WeaponGrid)
export default WeaponGrid

View file

@ -1,67 +0,0 @@
.WeaponGridMainhand {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 14px;
margin-right: 24px;
max-width: 200px;
}
.WeaponGridMainhand .WeaponGridImage {
display: flex;
align-items: center;
justify-content: center;
background: white;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 8px;
height: 420px;
overflow: hidden;
transition: all 0.18s ease-in-out;
width: 200px;
}
.editable .WeaponGridMainhand:hover {
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: rgba(0, 0, 0, 0.14) 0px 0px 14px;
cursor: pointer;
transform: scale(1.05, 1.05);
}
.WeaponGridMainhand h3 {
color: #333;
font-weight: 500;
margin: 0;
text-align: center;
}
.WeaponGridMainhand img {
position: relative;
width: 100%;
z-index: 2;
}
.WeaponGridImage .icon {
position: absolute;
color: #c9c9c9;
height: 20px;
width: 20px;
z-index: 1;
}
.WeaponGridImage:hover .icon {
color: #555;
}
.WeaponGridMainhand h3,
.WeaponGridMainhand ul {
display: none;
}
.filled h3 {
display: block;
}
.filled ul {
display: flex;
}

View file

@ -1,67 +0,0 @@
import React, { useEffect } from 'react'
import classnames from 'classnames'
import { useModal as useModal } from '~utils/useModal'
import SearchModal from '~components/SearchModal/SearchModal'
import UncapIndicator from '~components/UncapIndicator/UncapIndicator'
import mainhandImages from '../../images/mainhand/*.jpg'
import Plus from '../../../assets/plus.svg'
import './WeaponGridMainhand.css'
function WeaponGridMainhand(props: WeaponGridProps) {
const { open, openModal, closeModal } = useModal()
useEffect(() => {
// console.log('Mainhand weapon prop was updated.')
}, [props.weapon])
let imgSrc
if (props.weapon) {
const weapon = props.weapon!
if (process.env.NODE_ENV === 'development') {
imgSrc = mainhandImages[weapon.granblue_id]
} else if (process.env.NODE_ENV === 'production') {
imgSrc = `${process.env.SIERO_IMG_URL}/mainhand/${weapon.granblue_id}.jpg`
}
}
const openModalIfEditable = (props.editable) ? openModal : () => {}
const classes = classnames({
WeaponGridMainhand: true,
'editable': props.editable,
'filled': (props.weapon !== undefined)
})
const weapon = props.weapon
return (
<div>
<div className={classes} onClick={openModalIfEditable}>
<div className="WeaponGridImage">
<img className="grid_image" src={imgSrc} />
{ (props.editable) ? <span className='icon'><Plus /></span> : '' }
</div>
<UncapIndicator
ulb={weapon?.uncap.ulb || false}
flb={weapon?.uncap.flb || false }
uncapLevel={3}
/>
<h3 className="WeaponName">{weapon?.name.en}</h3>
</div>
{open ? (
<SearchModal
close={closeModal}
send={props.onReceiveData}
fromPosition={props.position}
placeholderText="Search for a weapon..."
/>
) : null}
</div>
)
}
export default WeaponGridMainhand

View file

@ -1,64 +0,0 @@
import React from 'react'
import classnames from 'classnames'
import { useModal as useModal } from '~utils/useModal'
import SearchModal from '~components/SearchModal/SearchModal'
import UncapIndicator from '~components/UncapIndicator/UncapIndicator'
import gridImages from '../../images/grid/*.jpg'
import Plus from '../../../assets/plus.svg'
import './WeaponGridUnit.css'
function WeaponGridUnit(props: WeaponGridProps) {
const { open, openModal, closeModal } = useModal()
let imgSrc
if (props.weapon) {
const weapon = props.weapon!
// Generate the correct source for the weapon
if (process.env.NODE_ENV === 'development') {
imgSrc = gridImages[weapon.granblue_id]
} else if (process.env.NODE_ENV === 'production') {
imgSrc = `${process.env.SIERO_IMG_URL}/grid/${weapon.granblue_id}.jpg`
}
}
const openModalIfEditable = (props.editable) ? openModal : () => {}
const classes = classnames({
WeaponGridUnit: true,
'editable': props.editable,
'filled': (props.weapon !== undefined)
})
const weapon = props.weapon
return (
<li>
<div className={classes} onClick={openModalIfEditable}>
<div className="WeaponGridImage">
<img className="grid_image" src={imgSrc} />
{ (props.editable) ? <span className='icon'><Plus /></span> : '' }
</div>
<UncapIndicator
ulb={weapon?.uncap.ulb || false}
flb={weapon?.uncap.flb || false}
uncapLevel={3}
/>
<h3 className="WeaponName">{weapon?.name.en}</h3>
</div>
{open ? (
<SearchModal
close={closeModal}
send={props.onReceiveData}
fromPosition={props.position}
placeholderText="Search for a weapon..."
/>
) : null}
</li>
)
}
export default WeaponGridUnit

View file

@ -1,47 +1,54 @@
.WeaponGridUnit {
.WeaponUnit {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 14px;
max-width: 160px;
}
.WeaponGridUnit .WeaponGridImage {
display: flex;
align-items: center;
justify-content: center;
.WeaponUnit .WeaponImage {
background: white;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 8px;
height: 92px;
list-style-type: none;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: all 0.18s ease-in-out;
width: 160px;
}
.editable .WeaponGridImage:hover {
.WeaponUnit.editable .WeaponImage:hover {
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: rgba(0, 0, 0, 0.14) 0px 0px 14px;
cursor: pointer;
transform: scale(1.1, 1.1);
}
.WeaponGridUnit h3 {
.WeaponUnit.filled h3 {
display: block;
}
.WeaponUnit.filled ul {
display: flex;
}
.WeaponUnit h3,
.WeaponUnit ul {
display: none;
}
.WeaponUnit h3 {
color: #333;
font-weight: 500;
margin: 0;
text-align: center;
}
.WeaponGridUnit img {
.WeaponUnit img {
position: relative;
width: 100%;
z-index: 2;
}
.WeaponGridImage .icon {
.WeaponImage .icon {
position: absolute;
color: #c9c9c9;
height: 20px;
@ -49,19 +56,28 @@
z-index: 1;
}
.WeaponGridImage:hover .icon {
.WeaponImage:hover .icon {
color: #555;
}
.WeaponGridUnit h3,
.WeaponGridUnit ul {
display: none;
/* Mainhand */
.WeaponUnit.mainhand {
margin-right: 24px;
max-width: 200px;
}
.filled h3 {
display: block;
.WeaponUnit.mainhand .WeaponImage {
height: 420px;
width: 200px;
}
.filled ul {
display: flex;
/* Grid */
.WeaponUnit.grid {
max-width: 160px;
}
.WeaponUnit.grid .WeaponImage {
list-style-type: none;
height: 92px;
width: 160px;
}

View file

@ -0,0 +1,96 @@
import React, { useEffect, useState } from 'react'
import classnames from 'classnames'
import { useModal as useModal } from '~utils/useModal'
import SearchModal from '~components/SearchModal/SearchModal'
import UncapIndicator from '~components/UncapIndicator/UncapIndicator'
import mainhandImages from '../../images/mainhand/*.jpg'
import gridImages from '../../images/grid/*.jpg'
import Plus from '../../../assets/plus.svg'
import './WeaponUnit.css'
interface Props {
onReceiveData: (weapon: Weapon, position: number) => void
weapon: Weapon | undefined
position: number
editable: boolean
unitType: 0 | 1
}
function WeaponUnit(props: Props) {
const [imageUrl, setImageUrl] = useState('')
const { open, openModal, closeModal } = useModal()
const openModalIfEditable = (props.editable) ? openModal : () => {}
const classes = classnames({
WeaponUnit: true,
'mainhand': props.unitType == 0,
'grid': props.unitType == 1,
'editable': props.editable,
'filled': (props.weapon !== undefined)
})
const weapon = props.weapon
useEffect(() => {
generateImageUrl()
})
function generateImageUrl() {
let imgSrc
if (props.weapon) {
const weapon = props.weapon!
// Generate the correct source for the weapon
if (process.env.NODE_ENV === 'development') {
if (props.unitType == 0)
imgSrc = mainhandImages[weapon.granblue_id]
else
imgSrc = gridImages[weapon.granblue_id]
} else if (process.env.NODE_ENV === 'production') {
if (props.unitType == 0)
imgSrc = `${process.env.SIERO_IMG_URL}/mainhand/${weapon.granblue_id}.jpg`
else
imgSrc = `${process.env.SIERO_IMG_URL}/grid/${weapon.granblue_id}.jpg`
}
}
setImageUrl(imgSrc)
}
return (
<div>
<div className={classes} onClick={openModalIfEditable}>
<div className="WeaponImage">
{
(imageUrl != '')
? <img className="grid_image" src={imageUrl} />
: <img className="grid_image" />
}
{ (props.editable) ? <span className='icon'><Plus /></span> : '' }
</div>
<UncapIndicator
ulb={weapon?.uncap.ulb || false}
flb={weapon?.uncap.flb || false}
uncapLevel={3}
/>
<h3 className="WeaponName">{weapon?.name.en}</h3>
</div>
{open ? (
<SearchModal
close={closeModal}
send={props.onReceiveData}
fromPosition={props.position}
placeholderText="Search for a weapon..."
/>
) : null}
</div>
)
}
export default WeaponUnit

View file

@ -1,24 +1,95 @@
import React from 'react'
import { RouteComponentProps, withRouter } from 'react-router'
import React, { useEffect, useState } from 'react'
import { withCookies, useCookies, Cookies } from 'react-cookie'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import api from '~utils/api'
import WeaponGrid from '../../components/WeaponGrid/WeaponGrid'
interface RouterProps {
interface Props {
hash: string
}
interface PartyProps extends RouteComponentProps<RouterProps> {
interface State {
found: boolean
editable: boolean
mainhand: Weapon,
grid: GridArray,
partyId: string
}
const Party: React.FC<PartyProps> = ({ match }) => {
interface PartyProps extends RouteComponentProps<Props> {}
type GridArray = { [key: number]: Weapon }
interface GridWeapon {
id: string
mainhand: boolean
position: number | null
weapon: Weapon
}
const Party: React.FC<PartyProps> = ({ match }, state: State) => {
const [found, setFound] = useState(false)
const [editable, setEditable] = useState(false)
const [grid, setGrid] = useState<GridArray>({})
const [mainhand, setMainhand] = useState<Weapon>()
const [partyId, setPartyId] = useState('')
const [cookies, setCookie] = useCookies(['userId'])
const shortcode = match.params.hash || ''
useEffect(() => {
fetchGrid(shortcode)
}, [])
async function fetchGrid(shortcode: string) {
return api.endpoints.parties.getOne({ id: shortcode })
.then(response => {
const party = response.data.party
if (party.user_id === cookies.user_id)
setEditable(true)
let weapons: GridArray = {}
party.grid.forEach((gridWeapon: GridWeapon) => {
if (gridWeapon.mainhand)
setMainhand(gridWeapon.weapon)
else if (!gridWeapon.mainhand && gridWeapon.position != null)
weapons[gridWeapon.position] = gridWeapon.weapon
})
setFound(true)
setGrid(weapons)
setPartyId(party.id)
})
.catch(error => {
if (error.response != null) {
if (error.response.status == 404) {
setFound(false)
}
} else {
console.error(error)
}
})
}
return (
<div>
<WeaponGrid shortcode={shortcode} editable={false} />
<WeaponGrid
userId={cookies.user_id}
partyId={partyId}
mainhand={mainhand}
grid={grid}
editable={editable}
exists={true}
found={found}
/>
</div>
)
}
export default withRouter(Party)
export default
withCookies(
withRouter(
Party
)
)