Implement state management for Weapon grid

Summon and Character will be next. I didn't really pay attention to code cleanliness, so I'll try to do a pass before merging the PR
This commit is contained in:
Justin Edmund 2022-02-23 01:51:58 -08:00
parent 2e36a0455d
commit 9b505f5e20
8 changed files with 255 additions and 247 deletions

View file

@ -10,6 +10,7 @@ interface Props {
found?: boolean found?: boolean
offset: number offset: number
onClick: (position: number) => void onClick: (position: number) => void
updateObject: (object: Character | Weapon | Summon, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void updateUncap: (id: string, position: number, uncap: number) => void
} }
@ -30,6 +31,7 @@ const ExtraWeapons = (props: Props) => {
unitType={1} unitType={1}
gridWeapon={props.grid[props.offset + i]} gridWeapon={props.grid[props.offset + i]}
onClick={() => { props.onClick(props.offset + i)}} onClick={() => { props.onClick(props.offset + i)}}
updateObject={props.updateObject}
updateUncap={props.updateUncap} updateUncap={props.updateUncap}
/> />
</li> </li>

View file

@ -22,9 +22,9 @@
overflow-y: auto; overflow-y: auto;
padding: $unit * 3; padding: $unit * 3;
position: relative; position: relative;
z-index: 10; z-index: 21;
#ModalTop { #ModalHeader {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import { useCookies } from 'react-cookie' import { useCookies } from 'react-cookie'
import PartyContext from '~context/PartyContext'
import PartySegmentedControl from '~components/PartySegmentedControl' import PartySegmentedControl from '~components/PartySegmentedControl'
import WeaponGrid from '~components/WeaponGrid' import WeaponGrid from '~components/WeaponGrid'
@ -9,6 +9,7 @@ import SummonGrid from '~components/SummonGrid'
import CharacterGrid from '~components/CharacterGrid' import CharacterGrid from '~components/CharacterGrid'
import api from '~utils/api' import api from '~utils/api'
import state from '~utils/state'
import { GridType, TeamElement } from '~utils/enums' import { GridType, TeamElement } from '~utils/enums'
import './index.scss' import './index.scss'
@ -29,12 +30,8 @@ const Party = (props: Props) => {
} : {} } : {}
// Set up states // Set up states
const { party } = useSnapshot(state)
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon) const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
const [id, setId] = useState('')
const [slug, setSlug] = useState('')
const [element, setElement] = useState<TeamElement>(TeamElement.Any)
const [editable, setEditable] = useState(false)
const [hasExtra, setHasExtra] = useState(false)
// Methods: Creating a new party // Methods: Creating a new party
async function createParty(extra: boolean = false) { async function createParty(extra: boolean = false) {
@ -50,10 +47,12 @@ const Party = (props: Props) => {
// Methods: Updating the party's extra flag // Methods: Updating the party's extra flag
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) { function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
setHasExtra(event.target.checked) if (party.id) {
api.endpoints.parties.update(id, { state.party.extra = event.target.checked
'party': { 'is_extra': event.target.checked } api.endpoints.parties.update(party.id, {
}, headers) 'party': { 'is_extra': event.target.checked }
}, headers)
}
} }
// Methods: Navigating with segmented control // Methods: Navigating with segmented control
@ -122,10 +121,8 @@ const Party = (props: Props) => {
return ( return (
<div> <div>
<PartyContext.Provider value={{ id, setId, slug, setSlug, element, setElement, editable, setEditable, hasExtra, setHasExtra }}> { navigation }
{ navigation } { currentGrid() }
{ currentGrid() }
</PartyContext.Provider>
</div> </div>
) )
} }

View file

@ -1,13 +1,14 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import './index.scss' import './index.scss'
import PartyContext from '~context/PartyContext' import state from '~utils/state'
import SegmentedControl from '~components/SegmentedControl' import SegmentedControl from '~components/SegmentedControl'
import Segment from '~components/Segment' import Segment from '~components/Segment'
import ToggleSwitch from '~components/ToggleSwitch' import ToggleSwitch from '~components/ToggleSwitch'
import { GridType } from '~utils/enums' import { GridType } from '~utils/enums'
import { useSnapshot } from 'valtio'
interface Props { interface Props {
selectedTab: GridType selectedTab: GridType
@ -16,10 +17,10 @@ interface Props {
} }
const PartySegmentedControl = (props: Props) => { const PartySegmentedControl = (props: Props) => {
const { editable, element, hasExtra } = useContext(PartyContext) const { party } = useSnapshot(state)
function getElement() { function getElement() {
switch(element) { switch(party.element) {
case 1: return "wind"; break case 1: return "wind"; break
case 2: return "fire"; break case 2: return "fire"; break
case 3: return "water"; break case 3: return "water"; break
@ -34,8 +35,8 @@ const PartySegmentedControl = (props: Props) => {
Extra Extra
<ToggleSwitch <ToggleSwitch
name="ExtraSwitch" name="ExtraSwitch"
editable={editable} editable={party.editable}
checked={hasExtra} checked={party.extra}
onChange={props.onCheckboxChange} onChange={props.onCheckboxChange}
/> />
</div> </div>
@ -74,7 +75,7 @@ const PartySegmentedControl = (props: Props) => {
{ {
(() => { (() => {
if (editable && props.selectedTab == GridType.Weapon) { if (party.editable && props.selectedTab == GridType.Weapon) {
return extraToggle return extraToggle
} }
})() })()

View file

@ -1,11 +1,11 @@
.ModalContainer .Modal.SearchModal { .Modal.Search {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 420px; min-height: 420px;
min-width: 600px; min-width: 600px;
padding: 0; padding: 0;
#ModalTop { #ModalHeader {
background: $grey-90; background: $grey-90;
gap: $unit; gap: $unit;
margin: 0; margin: 0;
@ -13,12 +13,28 @@
position: sticky; position: sticky;
top: 0; top: 0;
button {
background: transparent;
border: none;
height: 42px;
padding: 0;
svg {
height: 24px;
width: 24px;
vertical-align: middle;
}
}
label { label {
width: 100%; width: 100%;
.Input { .Input {
border: 1px solid $grey-70;
border-radius: $unit / 2;
box-sizing: border-box; box-sizing: border-box;
padding: 12px 8px; font-size: $font-regular;
padding: $unit * 1.5;
text-align: left; text-align: left;
width: 100%; width: 100%;
} }
@ -26,13 +42,13 @@
} }
} }
.SearchModal #results_container { .Search.Modal #results_container {
margin: 0; margin: 0;
max-height: 330px; max-height: 330px;
padding: 0 12px 12px 12px; padding: 0 12px 12px 12px;
} }
.SearchModal #NoResults { .Search.Modal #NoResults {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -40,7 +56,7 @@
flex-grow: 1; flex-grow: 1;
} }
.SearchModal #NoResults h2 { .Search.Modal #NoResults h2 {
color: #ccc; color: #ccc;
font-size: $font-large; font-size: $font-large;
font-weight: 500; font-weight: 500;

View file

@ -1,10 +1,11 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import { createPortal } from 'react-dom' import state from '~utils/state'
import api from '~utils/api' import api from '~utils/api'
import Modal from '~components/Modal' import * as Dialog from '@radix-ui/react-dialog'
import Overlay from '~components/Overlay'
import CharacterResult from '~components/CharacterResult' import CharacterResult from '~components/CharacterResult'
import WeaponResult from '~components/WeaponResult' import WeaponResult from '~components/WeaponResult'
import SummonResult from '~components/SummonResult' import SummonResult from '~components/SummonResult'
@ -13,138 +14,145 @@ import './index.scss'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
interface Props { interface Props {
close: () => void
send: (object: Character | Weapon | Summon, position: number) => any send: (object: Character | Weapon | Summon, position: number) => any
grid: GridArray<Character|Weapon|Summon>
placeholderText: string placeholderText: string
fromPosition: number fromPosition: number
object: 'weapons' | 'characters' | 'summons' object: 'weapons' | 'characters' | 'summons',
children: React.ReactNode
} }
interface State { const SearchModal = (props: Props) => {
query: string, let { grid } = useSnapshot(state)
results: { [key: string]: any }
loading: boolean
message: string
totalResults: number
}
class SearchModal extends React.Component<Props, State> { let searchInput = React.createRef<HTMLInputElement>()
searchInput: React.RefObject<HTMLInputElement>
constructor(props: Props) { const [pool, setPool] = useState(Array<Character | Weapon | Summon>())
super(props) const [open, setOpen] = useState(false)
this.state = { const [query, setQuery] = useState('')
query: '', const [results, setResults] = useState({})
results: {}, const [loading, setLoading] = useState(false)
loading: false, const [message, setMessage] = useState('')
message: '', const [totalResults, setTotalResults] = useState(0)
totalResults: 0
useEffect(() => {
if (props.object === 'characters') {
setPool(Object.values(grid.characters).map(o => o.character))
} else if (props.object === 'weapons') {
setPool(Object.values(grid.weapons.allWeapons).map(o => o.weapon))
} else if (props.object === 'summons') {
setPool(Object.values(grid.summons.allSummons).map(o => o.summon))
} }
this.searchInput = React.createRef<HTMLInputElement>() }, [grid, props.object])
useEffect(() => {
if (searchInput.current)
searchInput.current.focus()
}, [searchInput])
function filterExclusions(object: Character | Weapon | Summon) {
if (pool[props.fromPosition] &&
object.granblue_id === pool[props.fromPosition].granblue_id)
return null
else return object
} }
componentDidMount() { function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
if (this.searchInput.current) { const text = event.target.value
this.searchInput.current.focus() if (text.length) {
} setQuery(text)
} setLoading(true)
setMessage('')
filterExclusions = (o: Character | Weapon | Summon) => { if (text.length > 2)
if (this.props.grid[this.props.fromPosition] && fetchResults()
o.granblue_id == this.props.grid[this.props.fromPosition].granblue_id) {
return null
} else return o
}
fetchResults = (query: string) => {
const excludes = Object.values(this.props.grid).filter(this.filterExclusions).map((o) => { return o.name.en }).join(',')
api.search(this.props.object, query, excludes)
.then((response) => {
const data = response.data
const totalResults = data.length
this.setState({
results: data,
totalResults: totalResults,
loading: false
})
}, (error) => {
this.setState({
loading: false,
message: error
})
})
}
inputChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
const query = event.target.value
if (query.length) {
this.setState({ query, loading: true, message: '' }, () => {
this.fetchResults(query)
})
} else { } else {
this.setState({ query, results: {}, message: '' }) setQuery('')
setResults({})
setMessage('')
} }
} }
function fetchResults() {
const excludes = Object.values(pool)
.filter(filterExclusions)
.map((o) => { return o.name.en }).join(',')
sendData = (result: Character | Weapon | Summon) => { api.search(props.object, query, excludes)
this.props.send(result, this.props.fromPosition) .then(response => {
this.props.close() setResults(response.data)
setTotalResults(response.data.length)
setLoading(false)
})
.catch(error => {
setMessage(error)
setLoading(false)
})
} }
renderSearchResults = () => { function sendData(result: Character | Weapon | Summon) {
const { results } = this.state props.send(result, props.fromPosition)
setOpen(false)
switch(this.props.object) { }
function renderResults() {
switch(props.object) {
case 'weapons': case 'weapons':
return this.renderWeaponSearchResults(results) return renderWeaponSearchResults(results)
break
case 'summons': case 'summons':
return this.renderSummonSearchResults(results) return renderSummonSearchResults(results)
break
case 'characters': case 'characters':
return this.renderCharacterSearchResults(results) return renderCharacterSearchResults(results)
break
} }
} }
renderWeaponSearchResults = (results: { [key: string]: any }) => { function renderWeaponSearchResults(results: { [key: string]: any }) {
return ( const elements = results.map((result: Weapon) => {
<ul id="results_container"> return <WeaponResult
{ results.map( (result: Weapon) => { key={result.id}
return <WeaponResult key={result.id} data={result} onClick={() => { this.sendData(result) }} /> data={result}
})} onClick={() => { sendData(result) }}
</ul> />
) })
return (<ul id="results_container">{elements}</ul>)
} }
renderSummonSearchResults = (results: { [key: string]: any }) => { function renderSummonSearchResults(results: { [key: string]: any }) {
return ( const elements = results.map((result: Summon) => {
<ul id="results_container"> return <SummonResult
{ results.map( (result: Summon) => { key={result.id}
return <SummonResult key={result.id} data={result} onClick={() => { this.sendData(result) }} /> data={result}
})} onClick={() => { sendData(result) }}
</ul> />
) })
return (<ul id="results_container">{elements}</ul>)
} }
renderCharacterSearchResults = (results: { [key: string]: any }) => { function renderCharacterSearchResults(results: { [key: string]: any }) {
return ( const elements = results.map((result: Character) => {
<ul id="results_container"> return <CharacterResult
{ results.map( (result: Character) => { key={result.id}
return <CharacterResult key={result.id} data={result} onClick={() => { this.sendData(result) }} /> data={result}
})} onClick={() => { sendData(result) }}
</ul> />
) })
return (<ul id="results_container">{elements}</ul>)
} }
renderEmptyState = () => { function renderEmptyState() {
let string = '' let string = ''
if (this.state.query === '') { if (query === '') {
string = `No ${this.props.object}` string = `No ${props.object}`
} else if (query.length < 3) {
string = `Type at least 3 characters`
} else { } else {
string = `No results found for '${this.state.query}'` string = `No results found for '${query}'`
} }
return ( return (
@ -153,48 +161,46 @@ class SearchModal extends React.Component<Props, State> {
</div> </div>
) )
} }
render() { function resetAndClose() {
const { query, loading } = this.state setQuery('')
setResults({})
let content: JSX.Element setOpen(true)
if (Object.entries(this.state.results).length == 0) {
content = this.renderEmptyState()
} else {
content = this.renderSearchResults()
}
return (
createPortal(
<div>
<div className="ModalContainer">
<div className="Modal SearchModal" key="search_modal">
<div id="ModalTop">
<label className="search_label" htmlFor="search_input">
<input
autoComplete="off"
type="text"
name="query"
className="Input"
id="search_input"
ref={this.searchInput}
value={query}
placeholder={this.props.placeholderText}
onChange={this.inputChanged}
/>
</label>
<PlusIcon onClick={this.props.close} />
</div>
{content}
</div>
</div>
<Overlay onClick={this.props.close} />
</div>,
document.body
)
)
} }
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
{props.children}
</Dialog.Trigger>
<Dialog.Portal>
<div className="ModalContainer">
<Dialog.Content className="Search Modal">
<div id="ModalHeader">
<label className="search_label" htmlFor="search_input">
<input
autoComplete="off"
type="text"
name="query"
className="Input"
id="search_input"
ref={searchInput}
value={query}
placeholder={props.placeholderText}
onChange={inputChanged}
/>
</label>
<Dialog.Close onClick={resetAndClose}>
<PlusIcon />
</Dialog.Close>
</div>
{ ((Object.entries(results).length == 0) ? renderEmptyState() : renderResults()) }
</Dialog.Content>
</div>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
)
} }
export default SearchModal export default SearchModal

View file

@ -1,15 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react' import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useCookies } from 'react-cookie' import { useCookies } from 'react-cookie'
import { useSnapshot } from 'valtio'
import { useModal as useModal } from '~utils/useModal' import { useModal as useModal } from '~utils/useModal'
import state from '~utils/state'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import AppContext from '~context/AppContext'
import PartyContext from '~context/PartyContext'
import SearchModal from '~components/SearchModal'
import WeaponUnit from '~components/WeaponUnit' import WeaponUnit from '~components/WeaponUnit'
import ExtraWeapons from '~components/ExtraWeapons' import ExtraWeapons from '~components/ExtraWeapons'
@ -36,20 +35,11 @@ const WeaponGrid = (props: Props) => {
} : {} } : {}
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(state)
const [slug, setSlug] = useState()
const [found, setFound] = useState(false) const [found, setFound] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// Set up the party context
const { setEditable: setAppEditable } = useContext(AppContext)
const { id, setId } = useContext(PartyContext)
const { slug, setSlug } = useContext(PartyContext)
const { editable, setEditable } = useContext(PartyContext)
const { hasExtra, setHasExtra } = useContext(PartyContext)
const { setElement } = useContext(PartyContext)
// Set up states for Grid data
const [weapons, setWeapons] = useState<GridArray<GridWeapon>>({})
const [mainWeapon, setMainWeapon] = useState<GridWeapon>()
// Set up states for Search // Set up states for Search
const { open, openModal, closeModal } = useModal() const { open, openModal, closeModal } = useModal()
@ -66,28 +56,27 @@ const WeaponGrid = (props: Props) => {
const shortcode = (props.slug) ? props.slug : slug const shortcode = (props.slug) ? props.slug : slug
if (shortcode) fetchGrid(shortcode) if (shortcode) fetchGrid(shortcode)
else { else {
setEditable(true) state.party.editable = true
setAppEditable(true)
} }
}, [slug, props.slug]) }, [slug, props.slug])
// Initialize an array of current uncap values for each weapon // Initialize an array of current uncap values for each weapon
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: {[key: number]: number} = {} let initialPreviousUncapValues: {[key: number]: number} = {}
if (mainWeapon) initialPreviousUncapValues[-1] = mainWeapon.uncap_level if (state.grid.weapons.mainWeapon) initialPreviousUncapValues[-1] = state.grid.weapons.mainWeapon.uncap_level
Object.values(weapons).map(o => initialPreviousUncapValues[o.position] = o.uncap_level) Object.values(state.grid.weapons.allWeapons).map(o => initialPreviousUncapValues[o.position] = o.uncap_level)
setPreviousUncapValues(initialPreviousUncapValues) setPreviousUncapValues(initialPreviousUncapValues)
}, [mainWeapon, weapons]) }, [state.grid.weapons.mainWeapon, state.grid.weapons.allWeapons])
// Update search grid whenever weapons or the mainhand are updated // Update search grid whenever weapons or the mainhand are updated
useEffect(() => { useEffect(() => {
let newSearchGrid = Object.values(weapons).map((o) => o.weapon) let newSearchGrid = Object.values(grid.weapons.allWeapons).map((o) => o.weapon)
if (mainWeapon) if (state.grid.weapons.mainWeapon)
newSearchGrid.unshift(mainWeapon.weapon) newSearchGrid.unshift(state.grid.weapons.mainWeapon.weapon)
setSearchGrid(newSearchGrid) setSearchGrid(newSearchGrid)
}, [weapons, mainWeapon]) }, [state.grid.weapons.mainWeapon, state.grid.weapons.allWeapons])
// Methods: Fetching an object from the server // Methods: Fetching an object from the server
async function fetchGrid(shortcode: string) { async function fetchGrid(shortcode: string) {
@ -105,13 +94,13 @@ const WeaponGrid = (props: Props) => {
const loggedInUser = (cookies.user) ? cookies.user.user_id : '' const loggedInUser = (cookies.user) ? cookies.user.user_id : ''
if (partyUser != undefined && loggedInUser != undefined && partyUser === loggedInUser) { if (partyUser != undefined && loggedInUser != undefined && partyUser === loggedInUser) {
setEditable(true) state.party.editable = true
setAppEditable(true)
} }
// Store the important party and state-keeping values // Store the important party and state-keeping values
setId(party.id) state.party.id = party.id
setHasExtra(party.is_extra) state.party.extra = party.is_extra
setFound(true) setFound(true)
setLoading(false) setLoading(false)
@ -135,14 +124,12 @@ const WeaponGrid = (props: Props) => {
list.forEach((object: GridWeapon) => { list.forEach((object: GridWeapon) => {
if (object.mainhand) { if (object.mainhand) {
setMainWeapon(object) state.grid.weapons.mainWeapon = object
setElement(object.weapon.element) state.party.element = object.weapon.element
} else if (!object.mainhand && object.position != null) { } else if (!object.mainhand && object.position != null) {
weapons[object.position] = object state.grid.weapons.allWeapons[object.position] = object
} }
}) })
setWeapons(weapons)
} }
// Methods: Adding an object from search // Methods: Adding an object from search
@ -153,21 +140,23 @@ const WeaponGrid = (props: Props) => {
function receiveWeaponFromSearch(object: Character | Weapon | Summon, position: number) { function receiveWeaponFromSearch(object: Character | Weapon | Summon, position: number) {
const weapon = object as Weapon const weapon = object as Weapon
setElement(weapon.element) if (position == 1)
state.party.element = weapon.element
if (!id) { if (!party.id) {
props.createParty(hasExtra) props.createParty(party.extra)
.then(response => { .then(response => {
const party = response.data.party const party = response.data.party
setId(party.id) state.party.id = party.id
setSlug(party.shortcode) setSlug(party.shortcode)
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`) if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
saveWeapon(party.id, weapon, position) saveWeapon(party.id, weapon, position)
.then(response => storeGridWeapon(response.data.grid_weapon)) .then(response => storeGridWeapon(response.data.grid_weapon))
}) })
} else { } else {
saveWeapon(id, weapon, position) saveWeapon(party.id, weapon, position)
.then(response => storeGridWeapon(response.data.grid_weapon)) .then(response => storeGridWeapon(response.data.grid_weapon))
} }
} }
@ -190,12 +179,11 @@ const WeaponGrid = (props: Props) => {
function storeGridWeapon(gridWeapon: GridWeapon) { function storeGridWeapon(gridWeapon: GridWeapon) {
if (gridWeapon.position == -1) { if (gridWeapon.position == -1) {
setMainWeapon(gridWeapon) state.grid.weapons.mainWeapon = gridWeapon
state.party.element = gridWeapon.weapon.element
} else { } else {
// Store the grid unit at the correct position // Store the grid unit at the correct position
let newWeapons = Object.assign({}, weapons) state.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
newWeapons[gridWeapon.position] = gridWeapon
setWeapons(newWeapons)
} }
} }
@ -241,32 +229,30 @@ const WeaponGrid = (props: Props) => {
) )
const updateUncapLevel = (position: number, uncapLevel: number) => { const updateUncapLevel = (position: number, uncapLevel: number) => {
if (mainWeapon && position == -1) { if (state.grid.weapons.mainWeapon && position == -1)
mainWeapon.uncap_level = uncapLevel state.grid.weapons.mainWeapon.uncap_level = uncapLevel
setMainWeapon(mainWeapon) else
} else { state.grid.weapons.allWeapons[position].uncap_level = uncapLevel
let newWeapons = Object.assign({}, weapons)
newWeapons[position].uncap_level = uncapLevel
setWeapons(newWeapons)
}
} }
function storePreviousUncapValue(position: number) { function storePreviousUncapValue(position: number) {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = {...previousUncapValues} let newPreviousValues = {...previousUncapValues}
newPreviousValues[position] = (mainWeapon && position == -1) ? mainWeapon.uncap_level : weapons[position].uncap_level newPreviousValues[position] = (state.grid.weapons.mainWeapon && position == -1) ?
state.grid.weapons.mainWeapon.uncap_level : state.grid.weapons.allWeapons[position].uncap_level
setPreviousUncapValues(newPreviousValues) setPreviousUncapValues(newPreviousValues)
} }
// Render: JSX components // Render: JSX components
const mainhandElement = ( const mainhandElement = (
<WeaponUnit <WeaponUnit
gridWeapon={mainWeapon} gridWeapon={grid.weapons.mainWeapon}
editable={editable} editable={party.editable}
key="grid_mainhand" key="grid_mainhand"
position={-1} position={-1}
unitType={0} unitType={0}
onClick={() => { openSearchModal(-1) }} onClick={() => { openSearchModal(-1) }}
updateObject={receiveWeaponFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
) )
@ -276,11 +262,12 @@ const WeaponGrid = (props: Props) => {
return ( return (
<li key={`grid_unit_${i}`} > <li key={`grid_unit_${i}`} >
<WeaponUnit <WeaponUnit
gridWeapon={weapons[i]} gridWeapon={grid.weapons.allWeapons[i]}
editable={editable} editable={party.editable}
position={i} position={i}
unitType={1} unitType={1}
onClick={() => { openSearchModal(i) }} onClick={() => { openSearchModal(i) }}
updateObject={receiveWeaponFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
</li> </li>
@ -290,10 +277,11 @@ const WeaponGrid = (props: Props) => {
const extraGridElement = ( const extraGridElement = (
<ExtraWeapons <ExtraWeapons
grid={weapons} grid={state.grid.weapons.allWeapons}
editable={editable} editable={party.editable}
offset={numWeapons} offset={numWeapons}
onClick={openSearchModal} onClick={openSearchModal}
updateObject={receiveWeaponFromSearch}
updateUncap={initiateUncapUpdate} updateUncap={initiateUncapUpdate}
/> />
) )
@ -305,18 +293,7 @@ const WeaponGrid = (props: Props) => {
<ul className="grid_weapons">{ weaponGridElement }</ul> <ul className="grid_weapons">{ weaponGridElement }</ul>
</div> </div>
{ (() => { return (hasExtra) ? extraGridElement : '' })() } { (() => { return (party.extra) ? extraGridElement : '' })() }
{open ? (
<SearchModal
grid={searchGrid}
close={closeModal}
send={receiveWeaponFromSearch}
fromPosition={itemPositionForSearch}
object="weapons"
placeholderText="Search for a weapon..."
/>
) : null}
</div> </div>
) )
} }

View file

@ -5,6 +5,7 @@ import UncapIndicator from '~components/UncapIndicator'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
import './index.scss' import './index.scss'
import SearchModal from '~components/SearchModal'
interface Props { interface Props {
gridWeapon: GridWeapon | undefined gridWeapon: GridWeapon | undefined
@ -12,6 +13,7 @@ interface Props {
position: number position: number
editable: boolean editable: boolean
onClick: () => void onClick: () => void
updateObject: (object: Character | Weapon | Summon, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void updateUncap: (id: string, position: number, uncap: number) => void
} }
@ -55,10 +57,17 @@ const WeaponUnit = (props: Props) => {
return ( return (
<div> <div>
<div className={classes}> <div className={classes}>
<div className="WeaponImage" onClick={ (props.editable) ? props.onClick : () => {} }> <SearchModal
<img alt={weapon?.name.en} className="grid_image" src={imageUrl} /> placeholderText="Search for a weapon..."
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' } fromPosition={props.position}
</div> object="weapons"
send={props.updateObject}>
<div className="WeaponImage" onClick={ (props.editable) ? props.onClick : () => {} }>
<img alt={weapon?.name.en} className="grid_image" src={imageUrl} />
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' }
</div>
</SearchModal>
{ (gridWeapon) ? { (gridWeapon) ?
<UncapIndicator <UncapIndicator
type="weapon" type="weapon"