Refactor object grids to handle business logic instead of Party
This commit is contained in:
parent
44966fe8fe
commit
827473ee5a
16 changed files with 726 additions and 477 deletions
|
|
@ -1,11 +1,18 @@
|
|||
import React, { useState } from 'react'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { useModal as useModal } from '~utils/useModal'
|
||||
|
||||
import { AxiosResponse } from 'axios'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import CharacterUnit from '~components/CharacterUnit'
|
||||
import SearchModal from '~components/SearchModal'
|
||||
|
||||
import api from '~utils/api'
|
||||
import './index.scss'
|
||||
|
||||
// GridType
|
||||
export enum GridType {
|
||||
Class,
|
||||
Character,
|
||||
|
|
@ -13,67 +20,185 @@ export enum GridType {
|
|||
Summon
|
||||
}
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
userId?: string
|
||||
grid: GridArray<Character>
|
||||
partyId?: string
|
||||
characters: GridArray<GridCharacter>
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
onSelect: (type: GridType, character: Character, position: number) => void
|
||||
createParty: () => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
}
|
||||
|
||||
const CharacterGrid = (props: Props) => {
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [searchPosition, setSearchPosition] = useState(0)
|
||||
|
||||
// Constants
|
||||
const numCharacters: number = 5
|
||||
|
||||
function isCharacter(object: Character | Weapon | Summon): object is Character {
|
||||
// There aren't really any unique fields here
|
||||
return (object as Character).gender !== undefined
|
||||
}
|
||||
// Cookies
|
||||
const [cookies, _] = useCookies(['user'])
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.user.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Set up states for Grid data
|
||||
const [characters, setCharacters] = useState<GridArray<GridCharacter>>({})
|
||||
|
||||
// Set up states for Search
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [itemPositionForSearch, setItemPositionForSearch] = useState(0)
|
||||
|
||||
// Create a temporary state to store previous character uncap values
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
||||
|
||||
// Create a state dictionary to store pure objects for Search
|
||||
const [searchGrid, setSearchGrid] = useState<GridArray<Character>>({})
|
||||
|
||||
// Set states from props
|
||||
useEffect(() => {
|
||||
setCharacters(props.characters || {})
|
||||
}, [props])
|
||||
|
||||
// Update search grid whenever characters are updated
|
||||
useEffect(() => {
|
||||
let newSearchGrid = Object.values(characters).map((o) => o.character)
|
||||
setSearchGrid(newSearchGrid)
|
||||
}, [characters])
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function openSearchModal(position: number) {
|
||||
setSearchPosition(position)
|
||||
setItemPositionForSearch(position)
|
||||
openModal()
|
||||
}
|
||||
|
||||
function receiveCharacter(character: Character, position: number) {
|
||||
props.onSelect(GridType.Character, character, position)
|
||||
}
|
||||
function receiveCharacterFromSearch(object: Character | Weapon | Summon, position: number) {
|
||||
const character = object as Character
|
||||
|
||||
function sendData(object: Character | Weapon | Summon, position: number) {
|
||||
if (isCharacter(object)) {
|
||||
receiveCharacter(object, position)
|
||||
if (!props.partyId) {
|
||||
props.createParty()
|
||||
.then(response => {
|
||||
const party = response.data.party
|
||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||
saveCharacter(party.id, character, position)
|
||||
.then(response => storeGridCharacter(response.data.grid_character))
|
||||
})
|
||||
} else {
|
||||
saveCharacter(props.partyId, character, position)
|
||||
.then(response => storeGridCharacter(response.data.grid_character))
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCharacter(partyId: string, character: Character, position: number) {
|
||||
return await api.endpoints.characters.create({
|
||||
'character': {
|
||||
'party_id': partyId,
|
||||
'character_id': character.id,
|
||||
'position': position,
|
||||
'mainhand': (position == -1),
|
||||
'uncap_level': characterUncapLevel(character)
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
function storeGridCharacter(gridCharacter: GridCharacter) {
|
||||
// Store the grid unit at the correct position
|
||||
let newCharacters = Object.assign({}, characters)
|
||||
newCharacters[gridCharacter.position] = gridCharacter
|
||||
setCharacters(newCharacters)
|
||||
}
|
||||
|
||||
// Methods: Helpers
|
||||
function characterUncapLevel(character: Character) {
|
||||
let uncapLevel
|
||||
|
||||
if (character.special) {
|
||||
uncapLevel = 3
|
||||
if (character.uncap.ulb) uncapLevel = 5
|
||||
else if (character.uncap.flb) uncapLevel = 4
|
||||
} else {
|
||||
uncapLevel = 4
|
||||
if (character.uncap.ulb) uncapLevel = 6
|
||||
else if (character.uncap.flb) uncapLevel = 5
|
||||
}
|
||||
|
||||
return uncapLevel
|
||||
}
|
||||
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
try {
|
||||
await api.updateUncap('weapon', id, uncapLevel)
|
||||
.then(response => {
|
||||
storeGridCharacter(response.data.grid_character)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
}
|
||||
}
|
||||
|
||||
const initiateUncapUpdate = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
newPreviousValues[position] = characters[position].uncap_level
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
}, [previousUncapValues, characters]
|
||||
)
|
||||
|
||||
const debouncedAction = useMemo(() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
}, 1000), [saveUncap]
|
||||
)
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
let newCharacters = Object.assign({}, characters)
|
||||
newCharacters[position].uncap_level = uncapLevel
|
||||
setCharacters(newCharacters)
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
return (
|
||||
<div className="CharacterGrid">
|
||||
<ul id="grid_characters">
|
||||
{
|
||||
Array.from(Array(numCharacters)).map((x, i) => {
|
||||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<CharacterUnit
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
character={props.grid[i]}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
{Array.from(Array(numCharacters)).map((x, i) => {
|
||||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<CharacterUnit
|
||||
gridCharacter={props.characters[i]}
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
{open ? (
|
||||
<SearchModal
|
||||
grid={props.grid}
|
||||
close={closeModal}
|
||||
send={sendData}
|
||||
fromPosition={searchPosition}
|
||||
object="characters"
|
||||
placeholderText="Search for a character..."
|
||||
/>
|
||||
) : null}
|
||||
<SearchModal
|
||||
grid={searchGrid}
|
||||
close={closeModal}
|
||||
send={receiveCharacterFromSearch}
|
||||
fromPosition={itemPositionForSearch}
|
||||
object="characters"
|
||||
placeholderText="Search for a character..."
|
||||
/>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,45 +2,53 @@ import React, { useEffect, useState } from 'react'
|
|||
import classnames from 'classnames'
|
||||
|
||||
import UncapIndicator from '~components/UncapIndicator'
|
||||
|
||||
import PlusIcon from '~public/icons/plus.svg'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
onClick: () => void
|
||||
character: Character | undefined
|
||||
gridCharacter: GridCharacter | undefined
|
||||
position: number
|
||||
editable: boolean
|
||||
onClick: () => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
}
|
||||
|
||||
const CharacterUnit = (props: Props) => {
|
||||
console.log(props.gridCharacter?.character.name.en, props.gridCharacter?.uncap_level)
|
||||
|
||||
const [imageUrl, setImageUrl] = useState('')
|
||||
|
||||
const classes = classnames({
|
||||
CharacterUnit: true,
|
||||
'editable': props.editable,
|
||||
'filled': (props.character !== undefined)
|
||||
'filled': (props.gridCharacter !== undefined)
|
||||
})
|
||||
|
||||
const character = props.character
|
||||
const gridCharacter = props.gridCharacter
|
||||
const character = gridCharacter?.character
|
||||
|
||||
useEffect(() => {
|
||||
generateImageUrl()
|
||||
})
|
||||
|
||||
|
||||
function generateImageUrl() {
|
||||
let imgSrc = ""
|
||||
|
||||
if (props.character) {
|
||||
const character = props.character!
|
||||
if (props.gridCharacter) {
|
||||
const character = props.gridCharacter.character!
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_01.jpg`
|
||||
}
|
||||
|
||||
setImageUrl(imgSrc)
|
||||
}
|
||||
|
||||
function passUncapData(uncap: number) {
|
||||
console.log(`passuncapdata ${uncap}`)
|
||||
if (props.gridCharacter)
|
||||
props.updateUncap(props.gridCharacter.id, props.position, uncap)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes}>
|
||||
|
|
@ -48,11 +56,15 @@ const CharacterUnit = (props: Props) => {
|
|||
<img alt={character?.name.en} className="grid_image" src={imageUrl} />
|
||||
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' }
|
||||
</div>
|
||||
<UncapIndicator
|
||||
type="character"
|
||||
flb={character?.uncap.flb || false}
|
||||
uncapLevel={(character?.rarity == 2) ? 3 : 4}
|
||||
/>
|
||||
{ (gridCharacter && character) ?
|
||||
<UncapIndicator
|
||||
type="character"
|
||||
flb={character.uncap.flb || false}
|
||||
ulb={character.uncap.ulb || false}
|
||||
uncapLevel={gridCharacter.uncap_level}
|
||||
updateUncap={passUncapData}
|
||||
special={character.special}
|
||||
/> : '' }
|
||||
<h3 className="CharacterName">{character?.name.en}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface Props {
|
|||
found?: boolean
|
||||
offset: number
|
||||
onClick: (position: number) => void
|
||||
updateUncap: (id: string, uncap: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
}
|
||||
|
||||
const ExtraSummons = (props: Props) => {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,10 @@ export enum GridType {
|
|||
interface Props {
|
||||
grid: GridArray<GridWeapon>
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
found?: boolean
|
||||
offset: number
|
||||
onClick: (position: number) => void
|
||||
updateUncap: (id: string, uncap: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
}
|
||||
|
||||
const ExtraWeapons = (props: Props) => {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import React, { ChangeEvent, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import api from '~utils/api'
|
||||
|
||||
// UI Elements
|
||||
import PartySegmentedControl from '~components/PartySegmentedControl'
|
||||
|
||||
// Grids
|
||||
import WeaponGrid from '~components/WeaponGrid'
|
||||
import SummonGrid from '~components/SummonGrid'
|
||||
import CharacterGrid from '~components/CharacterGrid'
|
||||
|
||||
import api from '~utils/api'
|
||||
import './index.scss'
|
||||
|
||||
// GridType
|
||||
enum GridType {
|
||||
Class,
|
||||
|
|
@ -17,96 +16,58 @@ enum GridType {
|
|||
Weapon,
|
||||
Summon
|
||||
}
|
||||
export { GridType }
|
||||
|
||||
import './index.scss'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
partyId?: string
|
||||
mainWeapon?: GridWeapon
|
||||
mainSummon?: GridSummon
|
||||
friendSummon?: GridSummon
|
||||
characters?: GridArray<Character>
|
||||
characters?: GridArray<GridCharacter>
|
||||
weapons?: GridArray<GridWeapon>
|
||||
summons?: GridArray<GridSummon>
|
||||
extra: boolean
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
pushHistory?: (path: string) => void
|
||||
}
|
||||
|
||||
const Party = (props: Props) => {
|
||||
// Cookies
|
||||
const [cookies, _] = useCookies(['user'])
|
||||
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.user.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Grid data
|
||||
const [characters, setCharacters] = useState<GridArray<Character>>({})
|
||||
const [weapons, setWeapons] = useState<GridArray<GridWeapon>>({})
|
||||
const [summons, setSummons] = useState<GridArray<GridSummon>>({})
|
||||
|
||||
const [mainWeapon, setMainWeapon] = useState<GridWeapon>()
|
||||
const [mainSummon, setMainSummon] = useState<GridSummon>()
|
||||
const [friendSummon, setFriendSummon] = useState<GridSummon>()
|
||||
|
||||
// Set up states
|
||||
const [extra, setExtra] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
setPartyId(props.partyId || '')
|
||||
setMainWeapon(props.mainWeapon)
|
||||
setMainSummon(props.mainSummon)
|
||||
setFriendSummon(props.friendSummon)
|
||||
setCharacters(props.characters || {})
|
||||
setWeapons(props.weapons || {})
|
||||
setSummons(props.summons || {})
|
||||
setExtra(props.extra || false)
|
||||
}, [props.partyId, props.mainWeapon, props.mainSummon, props.friendSummon, props.characters, props.weapons, props.summons, props.extra])
|
||||
|
||||
const weaponGrid = (
|
||||
<WeaponGrid
|
||||
userId={cookies.user ? cookies.user.userId : ''}
|
||||
mainhand={mainWeapon}
|
||||
grid={weapons}
|
||||
editable={props.editable}
|
||||
exists={props.exists}
|
||||
extra={extra}
|
||||
onSelect={itemSelected}
|
||||
/>
|
||||
)
|
||||
|
||||
const summonGrid = (
|
||||
<SummonGrid
|
||||
userId={cookies.user ? cookies.user.userId : ''}
|
||||
main={mainSummon}
|
||||
friend={friendSummon}
|
||||
grid={summons}
|
||||
editable={props.editable}
|
||||
exists={props.exists}
|
||||
onSelect={itemSelected}
|
||||
/>
|
||||
)
|
||||
|
||||
const characterGrid = (
|
||||
<CharacterGrid
|
||||
userId={cookies.user ? cookies.user.userId : ''}
|
||||
grid={characters}
|
||||
editable={props.editable}
|
||||
exists={props.exists}
|
||||
onSelect={itemSelected}
|
||||
/>
|
||||
)
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
||||
const [partyId, setPartyId] = useState('')
|
||||
|
||||
// Set states from props
|
||||
useEffect(() => {
|
||||
setExtra(props.extra || false)
|
||||
}, [props])
|
||||
|
||||
// Methods: Creating a new party
|
||||
async function createParty() {
|
||||
let body = {
|
||||
party: {
|
||||
...(cookies.user) && { user_id: cookies.user.user_id },
|
||||
is_extra: extra
|
||||
}
|
||||
}
|
||||
|
||||
return await api.endpoints.parties.create(body, headers)
|
||||
}
|
||||
|
||||
// Methods: Updating the party's extra flag
|
||||
// Note: This doesn't save to the server yet.
|
||||
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setExtra(event.target.checked)
|
||||
}
|
||||
|
||||
// Methods: Navigating with segmented control
|
||||
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
switch(event.target.value) {
|
||||
case 'class':
|
||||
|
|
@ -126,174 +87,66 @@ const Party = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function itemSelected(type: GridType, item: Character | Weapon | Summon, position: number) {
|
||||
if (!partyId) {
|
||||
createParty()
|
||||
.then(response => {
|
||||
return response.data.party
|
||||
})
|
||||
.then(party => {
|
||||
if (props.pushHistory) {
|
||||
props.pushHistory(`/p/${party.shortcode}`)
|
||||
}
|
||||
// Render: JSX components
|
||||
const navigation = (
|
||||
<PartySegmentedControl
|
||||
extra={props.extra}
|
||||
editable={props.editable}
|
||||
selectedTab={currentTab}
|
||||
onClick={segmentClicked}
|
||||
onCheckboxChange={checkboxChanged}
|
||||
/>
|
||||
)
|
||||
|
||||
return party.id
|
||||
})
|
||||
.then(partyId => {
|
||||
setPartyId(partyId)
|
||||
saveItem(partyId, type, item, position)
|
||||
})
|
||||
} else {
|
||||
saveItem(partyId, type, item, position)
|
||||
}
|
||||
}
|
||||
const weaponGrid = (
|
||||
<WeaponGrid
|
||||
partyId={props.partyId}
|
||||
mainhand={props.mainWeapon}
|
||||
weapons={props.weapons || {}}
|
||||
extra={props.extra}
|
||||
editable={props.editable}
|
||||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
|
||||
async function createParty() {
|
||||
const body = (!cookies.user) ? {
|
||||
party: {
|
||||
is_extra: extra
|
||||
}
|
||||
} : {
|
||||
party: {
|
||||
user_id: cookies.user.userId,
|
||||
is_extra: extra
|
||||
}
|
||||
}
|
||||
const summonGrid = (
|
||||
<SummonGrid
|
||||
partyId={props.partyId}
|
||||
mainSummon={props.mainSummon}
|
||||
friendSummon={props.friendSummon}
|
||||
summons={props.summons || {}}
|
||||
editable={props.editable}
|
||||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
|
||||
return await api.endpoints.parties.create(body, headers)
|
||||
}
|
||||
const characterGrid = (
|
||||
<CharacterGrid
|
||||
partyId={props.partyId}
|
||||
characters={props.characters || {}}
|
||||
editable={props.editable}
|
||||
createParty={createParty}
|
||||
pushHistory={props.pushHistory}
|
||||
/>
|
||||
)
|
||||
|
||||
function saveItem(partyId: string, type: GridType, item: Character | Weapon | Summon, position: number) {
|
||||
switch(type) {
|
||||
case GridType.Class:
|
||||
saveClass()
|
||||
break
|
||||
const currentGrid = () => {
|
||||
switch(currentTab) {
|
||||
case GridType.Character:
|
||||
const character = item as Character
|
||||
saveCharacter(character, position, partyId)
|
||||
.then(() => {
|
||||
storeCharacter(character, position)
|
||||
})
|
||||
break
|
||||
return characterGrid
|
||||
case GridType.Weapon:
|
||||
const weapon = item as Weapon
|
||||
saveWeapon(weapon, position, partyId)
|
||||
.then((response) => {
|
||||
storeWeapon(response.data.grid_weapon)
|
||||
})
|
||||
break
|
||||
return weaponGrid
|
||||
case GridType.Summon:
|
||||
const summon = item as Summon
|
||||
saveSummon(summon, position, partyId)
|
||||
.then((response) => {
|
||||
storeSummon(response.data.grid_summon, position)
|
||||
})
|
||||
break
|
||||
return summonGrid
|
||||
}
|
||||
}
|
||||
|
||||
// Weapons
|
||||
function storeWeapon(weapon: GridWeapon) {
|
||||
if (weapon.position == -1) {
|
||||
setMainWeapon(weapon)
|
||||
} else {
|
||||
// Store the grid unit weapon at the correct position
|
||||
let newWeapons = Object.assign({}, weapons)
|
||||
newWeapons[weapon.position!] = weapon
|
||||
setWeapons(newWeapons)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveWeapon(weapon: Weapon, position: number, party: string) {
|
||||
let uncapLevel = 3
|
||||
|
||||
if (weapon.uncap.ulb)
|
||||
uncapLevel = 5
|
||||
else if (weapon.uncap.flb)
|
||||
uncapLevel = 4
|
||||
|
||||
return await api.endpoints.weapons.create({
|
||||
'weapon': {
|
||||
'party_id': party,
|
||||
'weapon_id': weapon.id,
|
||||
'position': position,
|
||||
'mainhand': (position == -1),
|
||||
'uncap_level': uncapLevel
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
// Summons
|
||||
function storeSummon(summon: GridSummon, position: number) {
|
||||
if (position == -1) {
|
||||
setMainSummon(summon)
|
||||
} else if (position == 6) {
|
||||
setFriendSummon(summon)
|
||||
} else {
|
||||
// Store the grid unit summon at the correct position
|
||||
let newSummons = Object.assign({}, summons)
|
||||
newSummons[position] = summon
|
||||
setSummons(newSummons)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSummon(summon: Summon, position: number, party: string) {
|
||||
return await api.endpoints.summons.create({
|
||||
'summon': {
|
||||
'party_id': party,
|
||||
'summon_id': summon.id,
|
||||
'position': position,
|
||||
'main': (position == -1),
|
||||
'friend': (position == 6)
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
// Character
|
||||
function storeCharacter(character: Character, position: number) {
|
||||
// Store the grid unit character at the correct position
|
||||
let newCharacters = Object.assign({}, characters)
|
||||
newCharacters[position] = character
|
||||
setCharacters(newCharacters)
|
||||
}
|
||||
|
||||
async function saveCharacter(character: Character, position: number, party: string) {
|
||||
await api.endpoints.characters.create({
|
||||
'character': {
|
||||
'party_id': party,
|
||||
'character_id': character.id,
|
||||
'position': position
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
// Class
|
||||
function saveClass() {
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PartySegmentedControl
|
||||
extra={extra}
|
||||
editable={props.editable}
|
||||
selectedTab={currentTab}
|
||||
onClick={segmentClicked}
|
||||
onCheckboxChange={checkboxChanged}
|
||||
/>
|
||||
|
||||
{
|
||||
(() => {
|
||||
switch(currentTab) {
|
||||
case GridType.Character:
|
||||
return characterGrid
|
||||
case GridType.Weapon:
|
||||
return weaponGrid
|
||||
case GridType.Summon:
|
||||
return summonGrid
|
||||
}
|
||||
})()
|
||||
}
|
||||
{ navigation }
|
||||
{ currentGrid() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { useModal as useModal } from '~utils/useModal'
|
||||
|
||||
import { AxiosResponse } from 'axios'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import SearchModal from '~components/SearchModal'
|
||||
|
|
@ -20,128 +23,237 @@ export enum GridType {
|
|||
|
||||
// Props
|
||||
interface Props {
|
||||
userId?: string
|
||||
partyId?: string
|
||||
main?: GridSummon | undefined
|
||||
friend?: GridSummon | undefined
|
||||
grid: GridArray<GridSummon>
|
||||
mainSummon: GridSummon | undefined
|
||||
friendSummon: GridSummon | undefined
|
||||
summons: GridArray<GridSummon>
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
found?: boolean
|
||||
onSelect: (type: GridType, summon: Summon, position: number) => void
|
||||
createParty: () => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
}
|
||||
|
||||
const SummonGrid = (props: Props) => {
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [searchPosition, setSearchPosition] = useState(0)
|
||||
|
||||
// Constants
|
||||
const numSummons: number = 4
|
||||
const searchGrid: GridArray<Summon> = Object.values(props.grid).map((o) => o.summon)
|
||||
|
||||
function receiveSummon(summon: Summon, position: number) {
|
||||
props.onSelect(GridType.Summon, summon, position)
|
||||
}
|
||||
|
||||
function sendData(object: Character | Weapon | Summon, position: number) {
|
||||
if (isSummon(object)) {
|
||||
receiveSummon(object, position)
|
||||
// Cookies
|
||||
const [cookies, _] = useCookies(['user'])
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.user.access_token}`
|
||||
}
|
||||
}
|
||||
} : {}
|
||||
|
||||
function isSummon(object: Character | Weapon | Summon): object is Summon {
|
||||
// There aren't really any unique fields here
|
||||
return (object as Summon).granblue_id !== undefined
|
||||
}
|
||||
// Set up states for Grid data
|
||||
const [summons, setSummons] = useState<GridArray<GridSummon>>({})
|
||||
const [mainSummon, setMainSummon] = useState<GridSummon>()
|
||||
const [friendSummon, setFriendSummon] = useState<GridSummon>()
|
||||
|
||||
// Set up states for Search
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [itemPositionForSearch, setItemPositionForSearch] = useState(0)
|
||||
|
||||
// Create a temporary state to store previous weapon uncap value
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
||||
|
||||
// Create a state dictionary to store pure objects for Search
|
||||
const [searchGrid, setSearchGrid] = useState<GridArray<Summon>>({})
|
||||
|
||||
// Set states from props
|
||||
useEffect(() => {
|
||||
setSummons(props.summons || {})
|
||||
setMainSummon(props.mainSummon)
|
||||
setFriendSummon(props.friendSummon)
|
||||
}, [props])
|
||||
|
||||
// Update search grid whenever any summon is updated
|
||||
useEffect(() => {
|
||||
let newSearchGrid = Object.values(summons).map((o) => o.summon)
|
||||
|
||||
if (mainSummon)
|
||||
newSearchGrid.unshift(mainSummon.summon)
|
||||
|
||||
if (friendSummon)
|
||||
newSearchGrid.unshift(friendSummon.summon)
|
||||
|
||||
setSearchGrid(newSearchGrid)
|
||||
}, [summons, mainSummon, friendSummon])
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function openSearchModal(position: number) {
|
||||
setSearchPosition(position)
|
||||
setItemPositionForSearch(position)
|
||||
openModal()
|
||||
}
|
||||
|
||||
async function updateUncap(id: string, level: number) {
|
||||
await api.updateUncap('summon', id, level)
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
function receiveSummonFromSearch(object: Character | Weapon | Summon, position: number) {
|
||||
const summon = object as Summon
|
||||
|
||||
if (!props.partyId) {
|
||||
props.createParty()
|
||||
.then(response => {
|
||||
const party = response.data.party
|
||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||
saveSummon(party.id, summon, position)
|
||||
.then(response => storeGridSummon(response.data.grid_summon))
|
||||
})
|
||||
} else {
|
||||
saveSummon(props.partyId, summon, position)
|
||||
.then(response => storeGridSummon(response.data.grid_summon))
|
||||
}
|
||||
}
|
||||
|
||||
const initiateUncapUpdate = (id: string, uncapLevel: number) => {
|
||||
debouncedAction(id, uncapLevel)
|
||||
async function saveSummon(partyId: string, summon: Summon, position: number) {
|
||||
let uncapLevel = 3
|
||||
if (summon.uncap.ulb) uncapLevel = 5
|
||||
else if (summon.uncap.flb) uncapLevel = 4
|
||||
|
||||
return await api.endpoints.summons.create({
|
||||
'summon': {
|
||||
'party_id': partyId,
|
||||
'summon_id': summon.id,
|
||||
'position': position,
|
||||
'main': (position == -1),
|
||||
'friend': (position == 6),
|
||||
'uncap_level': uncapLevel
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
const debouncedAction = useCallback(
|
||||
() => debounce((id, number) => {
|
||||
updateUncap(id, number)
|
||||
}, 1000), []
|
||||
)()
|
||||
function storeGridSummon(gridSummon: GridSummon) {
|
||||
if (gridSummon.position == -1) {
|
||||
setMainSummon(gridSummon)
|
||||
} else if (gridSummon.position == 6) {
|
||||
setFriendSummon(gridSummon)
|
||||
} else {
|
||||
// Store the grid unit at the correct position
|
||||
let newSummons = Object.assign({}, summons)
|
||||
newSummons[gridSummon.position] = gridSummon
|
||||
setSummons(newSummons)
|
||||
}
|
||||
}
|
||||
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
try {
|
||||
await api.updateUncap('summon', id, uncapLevel)
|
||||
.then(response => {
|
||||
storeGridSummon(response.data.grid_summon)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
}
|
||||
}
|
||||
|
||||
const initiateUncapUpdate = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
newPreviousValues[position] = summons[position].uncap_level
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
}, [previousUncapValues, summons]
|
||||
)
|
||||
|
||||
const debouncedAction = useMemo(() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
}, 1000), [saveUncap]
|
||||
)
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
let newSummons = Object.assign({}, summons)
|
||||
newSummons[position].uncap_level = uncapLevel
|
||||
setSummons(newSummons)
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
const mainSummonElement = (
|
||||
<div className="LabeledUnit">
|
||||
<div className="Label">Main Summon</div>
|
||||
<SummonUnit
|
||||
gridSummon={props.mainSummon}
|
||||
editable={props.editable}
|
||||
key="grid_main_summon"
|
||||
position={-1}
|
||||
unitType={0}
|
||||
onClick={() => { openSearchModal(-1) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const friendSummonElement = (
|
||||
<div className="LabeledUnit">
|
||||
<div className="Label">Friend Summon</div>
|
||||
<SummonUnit
|
||||
gridSummon={props.friendSummon}
|
||||
editable={props.editable}
|
||||
key="grid_friend_summon"
|
||||
position={6}
|
||||
unitType={2}
|
||||
onClick={() => { openSearchModal(6) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
const summonGridElement = (
|
||||
<div id="LabeledGrid">
|
||||
<div className="Label">Summons</div>
|
||||
<ul id="grid_summons">
|
||||
{Array.from(Array(numSummons)).map((x, i) => {
|
||||
return (<li key={`grid_unit_${i}`} >
|
||||
<SummonUnit
|
||||
gridSummon={props.summons[i]}
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
unitType={1}
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
const subAuraSummonElement = (
|
||||
<ExtraSummons
|
||||
grid={props.summons}
|
||||
editable={props.editable}
|
||||
exists={false}
|
||||
offset={numSummons}
|
||||
onClick={openSearchModal}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<div className="SummonGrid">
|
||||
<div className="LabeledUnit">
|
||||
<div className="Label">Main Summon</div>
|
||||
<SummonUnit
|
||||
editable={props.editable}
|
||||
key="grid_main_summon"
|
||||
position={-1}
|
||||
unitType={0}
|
||||
gridSummon={props.main}
|
||||
onClick={() => { openSearchModal(-1) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="LabeledUnit">
|
||||
<div className="Label">Friend Summon</div>
|
||||
<SummonUnit
|
||||
editable={props.editable}
|
||||
key="grid_friend_summon"
|
||||
position={6}
|
||||
unitType={2}
|
||||
gridSummon={props.friend}
|
||||
onClick={() => { openSearchModal(6) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="LabeledGrid">
|
||||
<div className="Label">Summons</div>
|
||||
<ul id="grid_summons">
|
||||
{
|
||||
Array.from(Array(numSummons)).map((x, i) => {
|
||||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<SummonUnit
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
unitType={1}
|
||||
gridSummon={props.grid[i]}
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
{ mainSummonElement }
|
||||
{ friendSummonElement }
|
||||
{ summonGridElement }
|
||||
</div>
|
||||
|
||||
<ExtraSummons
|
||||
grid={props.grid}
|
||||
editable={props.editable}
|
||||
exists={false}
|
||||
offset={numSummons}
|
||||
onClick={openSearchModal}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
{ subAuraSummonElement }
|
||||
|
||||
{open ? (
|
||||
<SearchModal
|
||||
grid={searchGrid}
|
||||
close={closeModal}
|
||||
send={sendData}
|
||||
fromPosition={searchPosition}
|
||||
send={receiveSummonFromSearch}
|
||||
fromPosition={itemPositionForSearch}
|
||||
object="summons"
|
||||
placeholderText="Search for a summon..."
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import './index.scss'
|
|||
|
||||
interface Props {
|
||||
onClick: () => void
|
||||
updateUncap: (id: string, uncap: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
gridSummon: GridSummon | undefined
|
||||
position: number
|
||||
editable: boolean
|
||||
|
|
@ -67,7 +67,8 @@ const SummonUnit = (props: Props) => {
|
|||
ulb={summon?.uncap.ulb || false}
|
||||
flb={summon?.uncap.flb || false}
|
||||
uncapLevel={gridSummon?.uncap_level}
|
||||
updateUncap={passUncapData}
|
||||
updateUncap={passUncapData}
|
||||
special={false}
|
||||
/> : '' }
|
||||
<h3 className="SummonName">{summon?.name.en}</h3>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,27 +8,36 @@ interface Props {
|
|||
rarity?: number
|
||||
uncapLevel: number
|
||||
flb: boolean
|
||||
ulb?: boolean
|
||||
ulb: boolean
|
||||
special: boolean
|
||||
updateUncap: (uncap: number) => void
|
||||
}
|
||||
|
||||
const UncapIndicator = (props: Props) => {
|
||||
const [uncap, setUncap] = useState(props.uncapLevel)
|
||||
|
||||
useEffect(() => {
|
||||
props.updateUncap(uncap)
|
||||
}, [uncap])
|
||||
|
||||
const numStars = setNumStars()
|
||||
function setNumStars() {
|
||||
let numStars
|
||||
|
||||
if (props.type === 'character') {
|
||||
if (props.flb) {
|
||||
numStars = 5
|
||||
if (props.special) {
|
||||
if (props.ulb) {
|
||||
numStars = 5
|
||||
} else if (props.flb) {
|
||||
numStars = 4
|
||||
} else {
|
||||
numStars = 3
|
||||
}
|
||||
} else {
|
||||
numStars = 4
|
||||
}
|
||||
if (props.ulb) {
|
||||
numStars = 6
|
||||
} else if (props.flb) {
|
||||
numStars = 5
|
||||
} else {
|
||||
numStars = 4
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.ulb) {
|
||||
numStars = 5
|
||||
|
|
@ -43,31 +52,38 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
|
||||
function toggleStar(index: number, empty: boolean) {
|
||||
if (empty) setUncap(index + 1)
|
||||
else setUncap(index)
|
||||
if (empty) props.updateUncap(index + 1)
|
||||
else props.updateUncap(index)
|
||||
}
|
||||
|
||||
const transcendence = (i: number) => {
|
||||
return <UncapStar ulb={true} empty={i >= uncap} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
return <UncapStar ulb={true} empty={i >= props.uncapLevel} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
}
|
||||
|
||||
const ulb = (i: number) => {
|
||||
return <UncapStar ulb={true} empty={i >= props.uncapLevel} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
}
|
||||
|
||||
const flb = (i: number) => {
|
||||
return <UncapStar flb={true} empty={i >= uncap} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
return <UncapStar flb={true} empty={i >= props.uncapLevel} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
}
|
||||
|
||||
const mlb = (i: number) => {
|
||||
return <UncapStar empty={i >= uncap} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
// console.log("MLB; Number of stars:", props.uncapLevel)
|
||||
return <UncapStar empty={i >= props.uncapLevel} key={`star_${i}`} index={i} onClick={toggleStar} />
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<ul className="UncapIndicator">
|
||||
{
|
||||
Array.from(Array(numStars)).map((x, i) => {
|
||||
if (props.type === 'character' && i > 4) {
|
||||
return transcendence(i)
|
||||
if (props.special)
|
||||
return ulb(i)
|
||||
else
|
||||
return transcendence(i)
|
||||
} else if (
|
||||
props.special && props.type === 'character' && i == 3 ||
|
||||
props.type === 'character' && i == 4 ||
|
||||
props.type !== 'character' && i > 2) {
|
||||
return flb(i)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { useModal as useModal } from '~utils/useModal'
|
||||
|
||||
import { AxiosResponse } from 'axios'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import SearchModal from '~components/SearchModal'
|
||||
|
|
@ -20,65 +23,199 @@ export enum GridType {
|
|||
|
||||
// Props
|
||||
interface Props {
|
||||
userId?: string
|
||||
partyId?: string
|
||||
mainhand?: GridWeapon | undefined
|
||||
grid: GridArray<GridWeapon>
|
||||
mainhand: GridWeapon | undefined
|
||||
weapons: GridArray<GridWeapon>
|
||||
extra: boolean
|
||||
editable: boolean
|
||||
exists: boolean
|
||||
found?: boolean
|
||||
onSelect: (type: GridType, weapon: Weapon, position: number) => void
|
||||
createParty: () => Promise<AxiosResponse<any, any>>
|
||||
pushHistory?: (path: string) => void
|
||||
}
|
||||
|
||||
const WeaponGrid = (props: Props) => {
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [searchPosition, setSearchPosition] = useState(0)
|
||||
|
||||
// Constants
|
||||
const numWeapons: number = 9
|
||||
const searchGrid: GridArray<Weapon> = Object.values(props.grid).map((o) => o.weapon)
|
||||
|
||||
function receiveWeapon(weapon: Weapon, position: number) {
|
||||
props.onSelect(GridType.Weapon, weapon, position)
|
||||
}
|
||||
|
||||
function sendData(object: Character | Weapon | Summon, position: number) {
|
||||
if (isWeapon(object)) {
|
||||
receiveWeapon(object, position)
|
||||
// Cookies
|
||||
const [cookies, _] = useCookies(['user'])
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.user.access_token}`
|
||||
}
|
||||
}
|
||||
} : {}
|
||||
|
||||
function isWeapon(object: Character | Weapon | Summon): object is Weapon {
|
||||
return (object as Weapon).proficiency !== undefined
|
||||
}
|
||||
// Set up states for Grid data
|
||||
const [weapons, setWeapons] = useState<GridArray<GridWeapon>>({})
|
||||
const [mainWeapon, setMainWeapon] = useState<GridWeapon>()
|
||||
|
||||
// Set up states for Search
|
||||
const { open, openModal, closeModal } = useModal()
|
||||
const [itemPositionForSearch, setItemPositionForSearch] = useState(0)
|
||||
|
||||
// Create a temporary state to store previous weapon uncap values
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
||||
|
||||
// Create a state dictionary to store pure objects for Search
|
||||
const [searchGrid, setSearchGrid] = useState<GridArray<Weapon>>({})
|
||||
|
||||
// Set states from props
|
||||
useEffect(() => {
|
||||
setWeapons(props.weapons || {})
|
||||
setMainWeapon(props.mainhand)
|
||||
}, [props])
|
||||
|
||||
// Update search grid whenever weapons or the mainhand are updated
|
||||
useEffect(() => {
|
||||
let newSearchGrid = Object.values(weapons).map((o) => o.weapon)
|
||||
|
||||
if (mainWeapon)
|
||||
newSearchGrid.unshift(mainWeapon.weapon)
|
||||
|
||||
setSearchGrid(newSearchGrid)
|
||||
}, [weapons, mainWeapon])
|
||||
|
||||
// Methods: Adding an object from search
|
||||
function openSearchModal(position: number) {
|
||||
setSearchPosition(position)
|
||||
setItemPositionForSearch(position)
|
||||
openModal()
|
||||
}
|
||||
|
||||
async function updateUncap(id: string, level: number) {
|
||||
await api.updateUncap('weapon', id, level)
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
function receiveWeaponFromSearch(object: Character | Weapon | Summon, position: number) {
|
||||
const weapon = object as Weapon
|
||||
|
||||
if (!props.partyId) {
|
||||
props.createParty()
|
||||
.then(response => {
|
||||
const party = response.data.party
|
||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||
saveWeapon(party.id, weapon, position)
|
||||
.then(response => storeGridWeapon(response.data.grid_weapon))
|
||||
})
|
||||
} else {
|
||||
saveWeapon(props.partyId, weapon, position)
|
||||
.then(response => storeGridWeapon(response.data.grid_weapon))
|
||||
}
|
||||
}
|
||||
|
||||
const initiateUncapUpdate = (id: string, uncapLevel: number) => {
|
||||
debouncedAction(id, uncapLevel)
|
||||
async function saveWeapon(partyId: string, weapon: Weapon, position: number) {
|
||||
let uncapLevel = 3
|
||||
if (weapon.uncap.ulb) uncapLevel = 5
|
||||
else if (weapon.uncap.flb) uncapLevel = 4
|
||||
|
||||
return await api.endpoints.weapons.create({
|
||||
'weapon': {
|
||||
'party_id': partyId,
|
||||
'weapon_id': weapon.id,
|
||||
'position': position,
|
||||
'mainhand': (position == -1),
|
||||
'uncap_level': uncapLevel
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
|
||||
const debouncedAction = useCallback(
|
||||
() => debounce((id, number) => {
|
||||
updateUncap(id, number)
|
||||
}, 1000), []
|
||||
)()
|
||||
function storeGridWeapon(gridWeapon: GridWeapon) {
|
||||
if (gridWeapon.position == -1) {
|
||||
setMainWeapon(gridWeapon)
|
||||
} else {
|
||||
// Store the grid unit at the correct position
|
||||
let newWeapons = Object.assign({}, props.weapons)
|
||||
newWeapons[gridWeapon.position] = gridWeapon
|
||||
setWeapons(newWeapons)
|
||||
}
|
||||
}
|
||||
|
||||
const extraGrid = (
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
// TODO: Don't make an API call if the new uncapLevel is the same as the current uncapLevel
|
||||
try {
|
||||
await api.updateUncap('weapon', id, uncapLevel)
|
||||
.then(response => {
|
||||
storeGridWeapon(response.data.grid_weapon)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
}
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
}, []
|
||||
)
|
||||
|
||||
function initiateUncapUpdate(id: string, position: number, uncapLevel: number) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = {...previousUncapValues}
|
||||
newPreviousValues[position] = (mainWeapon && position == -1) ? mainWeapon.uncap_level : weapons[position].uncap_level
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
|
||||
}
|
||||
|
||||
const debouncedAction = useMemo(() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
}, 1000), [saveUncap]
|
||||
)
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
if (mainWeapon && position == -1) {
|
||||
mainWeapon.uncap_level = uncapLevel
|
||||
setMainWeapon(mainWeapon)
|
||||
} else {
|
||||
let newWeapons = Object.assign({}, weapons)
|
||||
newWeapons[position].uncap_level = uncapLevel
|
||||
setWeapons(newWeapons)
|
||||
}
|
||||
}
|
||||
|
||||
// Render: JSX components
|
||||
const mainhandElement = (
|
||||
<WeaponUnit
|
||||
gridWeapon={mainWeapon}
|
||||
editable={props.editable}
|
||||
key="grid_mainhand"
|
||||
position={-1}
|
||||
unitType={0}
|
||||
onClick={() => { openSearchModal(-1) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)
|
||||
|
||||
const weaponGridElement = (
|
||||
Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<WeaponUnit
|
||||
gridWeapon={weapons[i]}
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
unitType={1}
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
const extraGridElement = (
|
||||
<ExtraWeapons
|
||||
grid={props.grid}
|
||||
grid={weapons}
|
||||
editable={props.editable}
|
||||
exists={false}
|
||||
offset={numWeapons}
|
||||
onClick={openSearchModal}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
|
|
@ -88,48 +225,18 @@ const WeaponGrid = (props: Props) => {
|
|||
return (
|
||||
<div id="weapon_grids">
|
||||
<div id="WeaponGrid">
|
||||
<WeaponUnit
|
||||
editable={props.editable}
|
||||
key="grid_mainhand"
|
||||
position={-1}
|
||||
unitType={0}
|
||||
gridWeapon={props.mainhand}
|
||||
onClick={() => { openSearchModal(-1) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
|
||||
<ul id="grid_weapons">
|
||||
{
|
||||
Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<WeaponUnit
|
||||
editable={props.editable}
|
||||
position={i}
|
||||
unitType={1}
|
||||
gridWeapon={props.grid[i]}
|
||||
onClick={() => { openSearchModal(i) }}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
{ mainhandElement }
|
||||
<ul id="grid_weapons">{ weaponGridElement }</ul>
|
||||
</div>
|
||||
|
||||
{ (() => {
|
||||
if(props.extra) {
|
||||
return extraGrid
|
||||
}
|
||||
})() }
|
||||
{ (() => { return (props.extra) ? extraGridElement : '' })() }
|
||||
|
||||
{open ? (
|
||||
<SearchModal
|
||||
grid={searchGrid}
|
||||
close={closeModal}
|
||||
send={sendData}
|
||||
fromPosition={searchPosition}
|
||||
send={receiveWeaponFromSearch}
|
||||
fromPosition={itemPositionForSearch}
|
||||
object="weapons"
|
||||
placeholderText="Search for a weapon..."
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import PlusIcon from '~public/icons/plus.svg'
|
|||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
onClick: () => void
|
||||
updateUncap: (id: string, uncap: number) => void
|
||||
gridWeapon: GridWeapon | undefined
|
||||
unitType: 0 | 1
|
||||
position: number
|
||||
editable: boolean
|
||||
unitType: 0 | 1
|
||||
onClick: () => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
}
|
||||
|
||||
const WeaponUnit = (props: Props) => {
|
||||
|
|
@ -48,8 +48,9 @@ const WeaponUnit = (props: Props) => {
|
|||
}
|
||||
|
||||
function passUncapData(uncap: number) {
|
||||
console.log("Passing uncap data to updateUncap callback...")
|
||||
if (props.gridWeapon)
|
||||
props.updateUncap(props.gridWeapon.id, uncap)
|
||||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -66,7 +67,8 @@ const WeaponUnit = (props: Props) => {
|
|||
ulb={gridWeapon.weapon.uncap.ulb || false}
|
||||
flb={gridWeapon.weapon.uncap.flb || false}
|
||||
uncapLevel={gridWeapon.uncap_level}
|
||||
updateUncap={passUncapData}
|
||||
updateUncap={passUncapData}
|
||||
special={false}
|
||||
/> : ''
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@radix-ui/react-label": "^0.1.4",
|
||||
"@radix-ui/react-switch": "^0.1.4",
|
||||
"@svgr/webpack": "^6.2.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"axios": "^0.25.0",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
|
|
@ -2982,6 +2983,15 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/axios": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
|
||||
"integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=",
|
||||
"deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!",
|
||||
"dependencies": {
|
||||
"axios": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
||||
|
|
@ -8754,6 +8764,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
|
||||
},
|
||||
"@types/axios": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
|
||||
"integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=",
|
||||
"requires": {
|
||||
"axios": "*"
|
||||
}
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"@radix-ui/react-label": "^0.1.4",
|
||||
"@radix-ui/react-switch": "^0.1.4",
|
||||
"@svgr/webpack": "^6.2.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"axios": "^0.25.0",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const PartyRoute: React.FC = () => {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [editable, setEditable] = useState(false)
|
||||
|
||||
const [characters, setCharacters] = useState<GridArray<Character>>({})
|
||||
const [characters, setCharacters] = useState<GridArray<GridCharacter>>({})
|
||||
const [weapons, setWeapons] = useState<GridArray<GridWeapon>>({})
|
||||
const [summons, setSummons] = useState<GridArray<GridSummon>>({})
|
||||
|
||||
|
|
@ -73,11 +73,11 @@ const PartyRoute: React.FC = () => {
|
|||
}
|
||||
|
||||
function populateCharacters(list: [GridCharacter]) {
|
||||
let characters: GridArray<Character> = {}
|
||||
let characters: GridArray<GridCharacter> = {}
|
||||
|
||||
list.forEach((object: GridCharacter) => {
|
||||
if (object.position != null)
|
||||
characters[object.position] = object.character
|
||||
characters[object.position] = object
|
||||
})
|
||||
|
||||
return characters
|
||||
|
|
|
|||
2
types/Character.d.ts
vendored
2
types/Character.d.ts
vendored
|
|
@ -21,6 +21,7 @@ interface Character {
|
|||
}
|
||||
uncap: {
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
}
|
||||
race: {
|
||||
race1: number
|
||||
|
|
@ -31,4 +32,5 @@ interface Character {
|
|||
proficiency2: number
|
||||
}
|
||||
position?: number
|
||||
special: boolean
|
||||
}
|
||||
3
types/GridCharacter.d.ts
vendored
3
types/GridCharacter.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
interface GridCharacter {
|
||||
id: string
|
||||
position: number | null
|
||||
position: number
|
||||
character: Character
|
||||
uncap_level: number
|
||||
}
|
||||
2
types/GridSummon.d.ts
vendored
2
types/GridSummon.d.ts
vendored
|
|
@ -2,7 +2,7 @@ interface GridSummon {
|
|||
id: string
|
||||
main: boolean
|
||||
friend: boolean
|
||||
position: number | null
|
||||
position: number
|
||||
summon: Summon
|
||||
uncap_level: number
|
||||
}
|
||||
Loading…
Reference in a new issue