Fix cookie usage in Party and grid tabs
This commit is contained in:
parent
cdf25a42bf
commit
a3d1c1ee56
4 changed files with 875 additions and 947 deletions
|
|
@ -1,254 +1,220 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useCookies } from 'react-cookie'
|
import { getCookie } from "cookies-next"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from "valtio"
|
||||||
|
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from "axios"
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from "lodash.debounce"
|
||||||
|
|
||||||
import JobSection from '~components/JobSection'
|
import JobSection from "~components/JobSection"
|
||||||
import CharacterUnit from '~components/CharacterUnit'
|
import CharacterUnit from "~components/CharacterUnit"
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from "~utils/api"
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
new: boolean
|
new: boolean
|
||||||
slug?: string
|
characters?: GridCharacter[]
|
||||||
createParty: () => Promise<AxiosResponse<any, any>>
|
createParty: () => Promise<AxiosResponse<any, any>>
|
||||||
pushHistory?: (path: string) => void
|
pushHistory?: (path: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterGrid = (props: Props) => {
|
const CharacterGrid = (props: Props) => {
|
||||||
// Constants
|
// Constants
|
||||||
const numCharacters: number = 5
|
const numCharacters: number = 5
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const [cookies] = useCookies(['account'])
|
const cookie = getCookie("account")
|
||||||
const headers = (cookies.account != null) ? {
|
const accountData: AccountCookie = cookie
|
||||||
headers: {
|
? JSON.parse(cookie as string)
|
||||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
: null
|
||||||
}
|
const headers = accountData
|
||||||
} : {}
|
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||||
|
: {}
|
||||||
|
|
||||||
// Set up state for view management
|
// Set up state for view management
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
const [slug, setSlug] = useState()
|
||||||
|
|
||||||
const [slug, setSlug] = useState()
|
// Create a temporary state to store previous character uncap values
|
||||||
const [found, setFound] = useState(false)
|
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||||
const [loading, setLoading] = useState(true)
|
[key: number]: number
|
||||||
const [firstLoadComplete, setFirstLoadComplete] = useState(false)
|
}>({})
|
||||||
|
|
||||||
// Create a temporary state to store previous character uncap values
|
// Set the editable flag only on first load
|
||||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
useEffect(() => {
|
||||||
|
// If user is logged in and matches
|
||||||
// Fetch data from the server
|
if (
|
||||||
useEffect(() => {
|
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||||
const shortcode = (props.slug) ? props.slug : slug
|
props.new
|
||||||
if (shortcode) fetchGrid(shortcode)
|
)
|
||||||
else appState.party.editable = true
|
appState.party.editable = true
|
||||||
}, [slug, props.slug])
|
else appState.party.editable = false
|
||||||
|
}, [props.new, accountData, party])
|
||||||
|
|
||||||
// Set the editable flag only on first load
|
// Initialize an array of current uncap values for each characters
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && !firstLoadComplete) {
|
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||||
// If user is logged in and matches
|
Object.values(appState.grid.characters).map(
|
||||||
if ((cookies.account && party.user && cookies.account.user_id === party.user.id) || props.new)
|
(o) => (initialPreviousUncapValues[o.position] = o.uncap_level)
|
||||||
appState.party.editable = true
|
)
|
||||||
else
|
setPreviousUncapValues(initialPreviousUncapValues)
|
||||||
appState.party.editable = false
|
}, [appState.grid.characters])
|
||||||
|
|
||||||
setFirstLoadComplete(true)
|
// Methods: Adding an object from search
|
||||||
}
|
function receiveCharacterFromSearch(
|
||||||
}, [props.new, cookies, party, loading, firstLoadComplete])
|
object: Character | Weapon | Summon,
|
||||||
|
position: number
|
||||||
|
) {
|
||||||
|
const character = object as Character
|
||||||
|
|
||||||
// Initialize an array of current uncap values for each characters
|
if (!party.id) {
|
||||||
useEffect(() => {
|
props.createParty().then((response) => {
|
||||||
let initialPreviousUncapValues: {[key: number]: number} = {}
|
const party = response.data.party
|
||||||
Object.values(appState.grid.characters).map(o => initialPreviousUncapValues[o.position] = o.uncap_level)
|
|
||||||
setPreviousUncapValues(initialPreviousUncapValues)
|
|
||||||
}, [appState.grid.characters])
|
|
||||||
|
|
||||||
// Methods: Fetching an object from the server
|
|
||||||
async function fetchGrid(shortcode: string) {
|
|
||||||
return api.endpoints.parties.getOneWithObject({ id: shortcode, object: 'characters', params: headers })
|
|
||||||
.then(response => processResult(response))
|
|
||||||
.catch(error => processError(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
function processResult(response: AxiosResponse) {
|
|
||||||
// Store the response
|
|
||||||
const party: Party = response.data.party
|
|
||||||
|
|
||||||
// Store the important party and state-keeping values
|
|
||||||
appState.party.id = party.id
|
appState.party.id = party.id
|
||||||
appState.party.user = party.user
|
setSlug(party.shortcode)
|
||||||
appState.party.favorited = party.favorited
|
|
||||||
appState.party.created_at = party.created_at
|
|
||||||
appState.party.updated_at = party.updated_at
|
|
||||||
|
|
||||||
setFound(true)
|
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
// Populate the weapons in state
|
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||||
populateCharacters(party.characters)
|
saveCharacter(party.id, character, position)
|
||||||
|
.then((response) => storeGridCharacter(response.data.grid_character))
|
||||||
|
.catch((error) => console.error(error))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (party.editable)
|
||||||
|
saveCharacter(party.id, character, position)
|
||||||
|
.then((response) => storeGridCharacter(response.data.grid_character))
|
||||||
|
.catch((error) => console.error(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
uncap_level: characterUncapLevel(character),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeGridCharacter(gridCharacter: GridCharacter) {
|
||||||
|
appState.grid.characters[gridCharacter.position] = gridCharacter
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
function processError(error: any) {
|
return uncapLevel
|
||||||
if (error.response != null) {
|
}
|
||||||
if (error.response.status == 404) {
|
|
||||||
setFound(false)
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateCharacters(list: Array<GridCharacter>) {
|
// Methods: Updating uncap level
|
||||||
list.forEach((object: GridCharacter) => {
|
// Note: Saves, but debouncing is not working properly
|
||||||
if (object.position != null)
|
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||||
appState.grid.characters[object.position] = object
|
storePreviousUncapValue(position)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (uncapLevel != previousUncapValues[position])
|
||||||
|
await api.updateUncap("character", 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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Adding an object from search
|
function initiateUncapUpdate(
|
||||||
function receiveCharacterFromSearch(object: Character | Weapon | Summon, position: number) {
|
id: string,
|
||||||
const character = object as Character
|
position: number,
|
||||||
|
uncapLevel: number
|
||||||
|
) {
|
||||||
|
memoizeAction(id, position, uncapLevel)
|
||||||
|
|
||||||
if (!party.id) {
|
// Optimistically update UI
|
||||||
props.createParty()
|
updateUncapLevel(position, uncapLevel)
|
||||||
.then(response => {
|
}
|
||||||
const party = response.data.party
|
|
||||||
appState.party.id = party.id
|
|
||||||
setSlug(party.shortcode)
|
|
||||||
|
|
||||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
const memoizeAction = useCallback(
|
||||||
saveCharacter(party.id, character, position)
|
(id: string, position: number, uncapLevel: number) => {
|
||||||
.then(response => storeGridCharacter(response.data.grid_character))
|
debouncedAction(id, position, uncapLevel)
|
||||||
.catch(error => console.error(error))
|
},
|
||||||
})
|
[props, previousUncapValues]
|
||||||
} else {
|
)
|
||||||
if (party.editable)
|
|
||||||
saveCharacter(party.id, character, position)
|
const debouncedAction = useMemo(
|
||||||
.then(response => storeGridCharacter(response.data.grid_character))
|
() =>
|
||||||
.catch(error => console.error(error))
|
debounce((id, position, number) => {
|
||||||
}
|
saveUncap(id, position, number)
|
||||||
|
}, 500),
|
||||||
|
[props, saveUncap]
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||||
|
appState.grid.characters[position].uncap_level = uncapLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
function storePreviousUncapValue(position: number) {
|
||||||
|
// Save the current value in case of an unexpected result
|
||||||
|
let newPreviousValues = { ...previousUncapValues }
|
||||||
|
|
||||||
|
if (grid.characters[position]) {
|
||||||
|
newPreviousValues[position] = grid.characters[position].uncap_level
|
||||||
|
setPreviousUncapValues(newPreviousValues)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveCharacter(partyId: string, character: Character, position: number) {
|
// Render: JSX components
|
||||||
return await api.endpoints.characters.create({
|
return (
|
||||||
'character': {
|
<div>
|
||||||
'party_id': partyId,
|
<div id="CharacterGrid">
|
||||||
'character_id': character.id,
|
<JobSection />
|
||||||
'position': position,
|
<ul id="grid_characters">
|
||||||
'uncap_level': characterUncapLevel(character)
|
{Array.from(Array(numCharacters)).map((x, i) => {
|
||||||
}
|
return (
|
||||||
}, headers)
|
<li key={`grid_unit_${i}`}>
|
||||||
}
|
<CharacterUnit
|
||||||
|
gridCharacter={grid.characters[i]}
|
||||||
function storeGridCharacter(gridCharacter: GridCharacter) {
|
editable={party.editable}
|
||||||
appState.grid.characters[gridCharacter.position] = gridCharacter
|
position={i}
|
||||||
}
|
updateObject={receiveCharacterFromSearch}
|
||||||
|
updateUncap={initiateUncapUpdate}
|
||||||
// Methods: Helpers
|
/>
|
||||||
function characterUncapLevel(character: Character) {
|
</li>
|
||||||
let uncapLevel
|
)
|
||||||
|
})}
|
||||||
if (character.special) {
|
</ul>
|
||||||
uncapLevel = 3
|
</div>
|
||||||
if (character.uncap.ulb) uncapLevel = 5
|
</div>
|
||||||
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) {
|
|
||||||
storePreviousUncapValue(position)
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (uncapLevel != previousUncapValues[position])
|
|
||||||
await api.updateUncap('character', 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initiateUncapUpdate(id: string, position: number, uncapLevel: number) {
|
|
||||||
memoizeAction(id, position, uncapLevel)
|
|
||||||
|
|
||||||
// Optimistically update UI
|
|
||||||
updateUncapLevel(position, uncapLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
const memoizeAction = useCallback(
|
|
||||||
(id: string, position: number, uncapLevel: number) => {
|
|
||||||
debouncedAction(id, position, uncapLevel)
|
|
||||||
}, [props, previousUncapValues]
|
|
||||||
)
|
|
||||||
|
|
||||||
const debouncedAction = useMemo(() =>
|
|
||||||
debounce((id, position, number) => {
|
|
||||||
saveUncap(id, position, number)
|
|
||||||
}, 500), [props, saveUncap]
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
|
||||||
appState.grid.characters[position].uncap_level = uncapLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
function storePreviousUncapValue(position: number) {
|
|
||||||
// Save the current value in case of an unexpected result
|
|
||||||
let newPreviousValues = {...previousUncapValues}
|
|
||||||
|
|
||||||
if (grid.characters[position]) {
|
|
||||||
newPreviousValues[position] = grid.characters[position].uncap_level
|
|
||||||
setPreviousUncapValues(newPreviousValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render: JSX components
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div id="CharacterGrid">
|
|
||||||
<JobSection />
|
|
||||||
<ul id="grid_characters">
|
|
||||||
{Array.from(Array(numCharacters)).map((x, i) => {
|
|
||||||
return (
|
|
||||||
<li key={`grid_unit_${i}`} >
|
|
||||||
<CharacterUnit
|
|
||||||
gridCharacter={grid.characters[i]}
|
|
||||||
editable={party.editable}
|
|
||||||
position={i}
|
|
||||||
updateObject={receiveCharacterFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterGrid
|
export default CharacterGrid
|
||||||
|
|
|
||||||
|
|
@ -1,254 +1,286 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from "next/router"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from "valtio"
|
||||||
import { useCookies } from 'react-cookie'
|
import { getCookie } from "cookies-next"
|
||||||
import clonedeep from 'lodash.clonedeep'
|
import clonedeep from "lodash.clonedeep"
|
||||||
import { subscribeKey } from 'valtio/utils'
|
|
||||||
|
|
||||||
import PartySegmentedControl from '~components/PartySegmentedControl'
|
import PartySegmentedControl from "~components/PartySegmentedControl"
|
||||||
import PartyDetails from '~components/PartyDetails'
|
import PartyDetails from "~components/PartyDetails"
|
||||||
import WeaponGrid from '~components/WeaponGrid'
|
import WeaponGrid from "~components/WeaponGrid"
|
||||||
import SummonGrid from '~components/SummonGrid'
|
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 { appState, initialAppState } from '~utils/appState'
|
import { appState, initialAppState } from "~utils/appState"
|
||||||
import { GridType, TeamElement } from '~utils/enums'
|
import { GridType, TeamElement } from "~utils/enums"
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
import { AxiosResponse } from 'axios'
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
new?: boolean
|
new?: boolean
|
||||||
slug?: string
|
team?: Party
|
||||||
pushHistory?: (path: string) => void
|
raids: Raid[][]
|
||||||
|
pushHistory?: (path: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Party = (props: Props) => {
|
const Party = (props: Props) => {
|
||||||
// Cookies
|
// Cookies
|
||||||
const [cookies] = useCookies(['account'])
|
const cookie = getCookie("account")
|
||||||
const headers = useMemo(() => {
|
const accountData: AccountCookie = cookie
|
||||||
return (cookies.account != null) ? {
|
? JSON.parse(cookie as string)
|
||||||
headers: { 'Authorization': `Bearer ${cookies.account.access_token}` }
|
: null
|
||||||
} : {}
|
|
||||||
}, [cookies.account])
|
|
||||||
|
|
||||||
// Set up router
|
const headers = useMemo(() => {
|
||||||
const router = useRouter()
|
return accountData
|
||||||
|
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||||
|
: {}
|
||||||
|
}, [accountData])
|
||||||
|
|
||||||
// Set up states
|
// Set up router
|
||||||
const { party } = useSnapshot(appState)
|
const router = useRouter()
|
||||||
const jobState = party.job
|
|
||||||
|
|
||||||
const [job, setJob] = useState<Job>()
|
// Set up states
|
||||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
const { party } = useSnapshot(appState)
|
||||||
|
const jobState = party.job
|
||||||
|
|
||||||
// Reset state on first load
|
const [job, setJob] = useState<Job>()
|
||||||
useEffect(() => {
|
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
||||||
const resetState = clonedeep(initialAppState)
|
|
||||||
appState.grid = resetState.grid
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Reset state on first load
|
||||||
setJob(jobState)
|
useEffect(() => {
|
||||||
}, [jobState])
|
const resetState = clonedeep(initialAppState)
|
||||||
|
appState.grid = resetState.grid
|
||||||
|
if (props.team) storeParty(props.team)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
jobChanged()
|
setJob(jobState)
|
||||||
}, [job])
|
}, [jobState])
|
||||||
|
|
||||||
// Methods: Creating a new party
|
useEffect(() => {
|
||||||
async function createParty(extra: boolean = false) {
|
jobChanged()
|
||||||
let body = {
|
}, [job])
|
||||||
party: {
|
|
||||||
...(cookies.account) && { user_id: cookies.account.user_id },
|
|
||||||
extra: extra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await api.endpoints.parties.create(body, headers)
|
// Methods: Creating a new party
|
||||||
|
async function createParty(extra: boolean = false) {
|
||||||
|
let body = {
|
||||||
|
party: {
|
||||||
|
...(accountData && { user_id: accountData.userId }),
|
||||||
|
extra: extra,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Updating the party's details
|
return await api.endpoints.parties.create(body, headers)
|
||||||
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
}
|
||||||
appState.party.extra = event.target.checked
|
|
||||||
|
|
||||||
if (party.id) {
|
// Methods: Updating the party's details
|
||||||
api.endpoints.parties.update(party.id, {
|
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
'party': { 'extra': event.target.checked }
|
appState.party.extra = event.target.checked
|
||||||
}, headers)
|
|
||||||
}
|
if (party.id) {
|
||||||
|
api.endpoints.parties.update(
|
||||||
|
party.id,
|
||||||
|
{
|
||||||
|
party: { extra: event.target.checked },
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function jobChanged() {
|
function jobChanged() {
|
||||||
if (party.id) {
|
if (party.id) {
|
||||||
api.endpoints.parties.update(party.id, {
|
api.endpoints.parties.update(
|
||||||
'party': { 'job_id': (job) ? job.id : '' }
|
party.id,
|
||||||
}, headers)
|
{
|
||||||
}
|
party: { job_id: job ? job.id : "" },
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateDetails(name?: string, description?: string, raid?: Raid) {
|
function updateDetails(name?: string, description?: string, raid?: Raid) {
|
||||||
if (appState.party.name !== name ||
|
if (
|
||||||
appState.party.description !== description ||
|
appState.party.name !== name ||
|
||||||
appState.party.raid?.id !== raid?.id) {
|
appState.party.description !== description ||
|
||||||
if (appState.party.id)
|
appState.party.raid?.id !== raid?.id
|
||||||
api.endpoints.parties.update(appState.party.id, {
|
) {
|
||||||
'party': {
|
if (appState.party.id)
|
||||||
'name': name,
|
api.endpoints.parties
|
||||||
'description': description,
|
.update(
|
||||||
'raid_id': raid?.id
|
appState.party.id,
|
||||||
}
|
{
|
||||||
}, headers)
|
party: {
|
||||||
.then(() => {
|
name: name,
|
||||||
appState.party.name = name
|
description: description,
|
||||||
appState.party.description = description
|
raid_id: raid?.id,
|
||||||
appState.party.raid = raid
|
},
|
||||||
appState.party.updated_at = party.updated_at
|
},
|
||||||
})
|
headers
|
||||||
}
|
)
|
||||||
|
.then(() => {
|
||||||
|
appState.party.name = name
|
||||||
|
appState.party.description = description
|
||||||
|
appState.party.raid = raid
|
||||||
|
appState.party.updated_at = party.updated_at
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Deleting the party
|
// Deleting the party
|
||||||
function deleteTeam(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
function deleteTeam(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||||
if (appState.party.editable && appState.party.id) {
|
if (appState.party.editable && appState.party.id) {
|
||||||
api.endpoints.parties.destroy({ id: appState.party.id, params: headers })
|
api.endpoints.parties
|
||||||
.then(() => {
|
.destroy({ id: appState.party.id, params: headers })
|
||||||
// Push to route
|
.then(() => {
|
||||||
router.push('/')
|
// Push to route
|
||||||
|
router.push("/")
|
||||||
|
|
||||||
// Clean state
|
// Clean state
|
||||||
const resetState = clonedeep(initialAppState)
|
const resetState = clonedeep(initialAppState)
|
||||||
Object.keys(resetState).forEach((key) => {
|
Object.keys(resetState).forEach((key) => {
|
||||||
appState[key] = resetState[key]
|
appState[key] = resetState[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set party to be editable
|
// Set party to be editable
|
||||||
appState.party.editable = true
|
appState.party.editable = true
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Navigating with segmented control
|
// Methods: Storing party data
|
||||||
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
const storeParty = function (party: Party) {
|
||||||
switch(event.target.value) {
|
// Store the important party and state-keeping values
|
||||||
case 'class':
|
appState.party.id = party.id
|
||||||
setCurrentTab(GridType.Class)
|
appState.party.extra = party.extra
|
||||||
break
|
appState.party.user = party.user
|
||||||
case 'characters':
|
appState.party.favorited = party.favorited
|
||||||
setCurrentTab(GridType.Character)
|
appState.party.created_at = party.created_at
|
||||||
break
|
appState.party.updated_at = party.updated_at
|
||||||
case 'weapons':
|
|
||||||
setCurrentTab(GridType.Weapon)
|
// Populate state
|
||||||
break
|
storeCharacters(party.characters)
|
||||||
case 'summons':
|
storeWeapons(party.weapons)
|
||||||
setCurrentTab(GridType.Summon)
|
storeSummons(party.summons)
|
||||||
break
|
}
|
||||||
default:
|
|
||||||
break
|
const storeCharacters = (list: Array<GridCharacter>) => {
|
||||||
}
|
list.forEach((object: GridCharacter) => {
|
||||||
|
if (object.position != null)
|
||||||
|
appState.grid.characters[object.position] = object
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeWeapons = (list: Array<GridWeapon>) => {
|
||||||
|
list.forEach((gridObject: GridWeapon) => {
|
||||||
|
if (gridObject.mainhand) {
|
||||||
|
appState.grid.weapons.mainWeapon = gridObject
|
||||||
|
appState.party.element = gridObject.object.element
|
||||||
|
} else if (!gridObject.mainhand && gridObject.position != null) {
|
||||||
|
appState.grid.weapons.allWeapons[gridObject.position] = gridObject
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeSummons = (list: Array<GridSummon>) => {
|
||||||
|
list.forEach((gridObject: GridSummon) => {
|
||||||
|
if (gridObject.main) appState.grid.summons.mainSummon = gridObject
|
||||||
|
else if (gridObject.friend)
|
||||||
|
appState.grid.summons.friendSummon = gridObject
|
||||||
|
else if (
|
||||||
|
!gridObject.main &&
|
||||||
|
!gridObject.friend &&
|
||||||
|
gridObject.position != null
|
||||||
|
)
|
||||||
|
appState.grid.summons.allSummons[gridObject.position] = gridObject
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Navigating with segmented control
|
||||||
|
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
switch (event.target.value) {
|
||||||
|
case "class":
|
||||||
|
setCurrentTab(GridType.Class)
|
||||||
|
break
|
||||||
|
case "characters":
|
||||||
|
setCurrentTab(GridType.Character)
|
||||||
|
break
|
||||||
|
case "weapons":
|
||||||
|
setCurrentTab(GridType.Weapon)
|
||||||
|
break
|
||||||
|
case "summons":
|
||||||
|
setCurrentTab(GridType.Summon)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Fetch party details
|
// Render: JSX components
|
||||||
const processResult = useCallback((response: AxiosResponse) => {
|
const navigation = (
|
||||||
appState.party.id = response.data.party.id
|
<PartySegmentedControl
|
||||||
appState.party.user = response.data.party.user
|
selectedTab={currentTab}
|
||||||
appState.party.favorited = response.data.party.favorited
|
onClick={segmentClicked}
|
||||||
appState.party.created_at = response.data.party.created_at
|
onCheckboxChange={checkboxChanged}
|
||||||
appState.party.updated_at = response.data.party.updated_at
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
// Store the party's user-generated details
|
const weaponGrid = (
|
||||||
appState.party.name = response.data.party.name
|
<WeaponGrid
|
||||||
appState.party.description = response.data.party.description
|
new={props.new || false}
|
||||||
appState.party.raid = response.data.party.raid
|
weapons={props.team?.weapons}
|
||||||
appState.party.job = response.data.party.job
|
createParty={createParty}
|
||||||
}, [])
|
pushHistory={props.pushHistory}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
const handleError = useCallback((error: any) => {
|
const summonGrid = (
|
||||||
if (error.response != null && error.response.status == 404) {
|
<SummonGrid
|
||||||
// setFound(false)
|
new={props.new || false}
|
||||||
} else if (error.response != null) {
|
summons={props.team?.summons}
|
||||||
console.error(error)
|
createParty={createParty}
|
||||||
} else {
|
pushHistory={props.pushHistory}
|
||||||
console.error("There was an error.")
|
/>
|
||||||
}
|
)
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchDetails = useCallback((shortcode: string) => {
|
const characterGrid = (
|
||||||
return api.endpoints.parties.getOne({ id: shortcode, params: headers })
|
<CharacterGrid
|
||||||
.then(response => processResult(response))
|
new={props.new || false}
|
||||||
.catch(error => handleError(error))
|
characters={props.team?.characters}
|
||||||
}, [headers, processResult, handleError])
|
createParty={createParty}
|
||||||
|
pushHistory={props.pushHistory}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
const currentGrid = () => {
|
||||||
const shortcode = (props.slug) ? props.slug : undefined
|
switch (currentTab) {
|
||||||
if (shortcode) fetchDetails(shortcode)
|
case GridType.Character:
|
||||||
}, [props.slug, fetchDetails])
|
return characterGrid
|
||||||
|
case GridType.Weapon:
|
||||||
|
return weaponGrid
|
||||||
|
case GridType.Summon:
|
||||||
|
return summonGrid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render: JSX components
|
return (
|
||||||
const navigation = (
|
<div>
|
||||||
<PartySegmentedControl
|
{navigation}
|
||||||
selectedTab={currentTab}
|
<section id="Party">{currentGrid()}</section>
|
||||||
onClick={segmentClicked}
|
{
|
||||||
onCheckboxChange={checkboxChanged}
|
<PartyDetails
|
||||||
|
editable={party.editable}
|
||||||
|
updateCallback={updateDetails}
|
||||||
|
deleteCallback={deleteTeam}
|
||||||
/>
|
/>
|
||||||
)
|
}
|
||||||
|
</div>
|
||||||
const weaponGrid = (
|
)
|
||||||
<WeaponGrid
|
|
||||||
new={props.new || false}
|
|
||||||
slug={props.slug}
|
|
||||||
createParty={createParty}
|
|
||||||
pushHistory={props.pushHistory}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const summonGrid = (
|
|
||||||
<SummonGrid
|
|
||||||
new={props.new || false}
|
|
||||||
slug={props.slug}
|
|
||||||
createParty={createParty}
|
|
||||||
pushHistory={props.pushHistory}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const characterGrid = (
|
|
||||||
<CharacterGrid
|
|
||||||
new={props.new || false}
|
|
||||||
slug={props.slug}
|
|
||||||
createParty={createParty}
|
|
||||||
pushHistory={props.pushHistory}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentGrid = () => {
|
|
||||||
switch(currentTab) {
|
|
||||||
case GridType.Character:
|
|
||||||
return characterGrid
|
|
||||||
case GridType.Weapon:
|
|
||||||
return weaponGrid
|
|
||||||
case GridType.Summon:
|
|
||||||
return summonGrid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ navigation }
|
|
||||||
<section id="Party">
|
|
||||||
{ currentGrid() }
|
|
||||||
</section>
|
|
||||||
{ <PartyDetails
|
|
||||||
editable={party.editable}
|
|
||||||
updateCallback={updateDetails}
|
|
||||||
deleteCallback={deleteTeam}
|
|
||||||
/>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Party
|
export default Party
|
||||||
|
|
|
||||||
|
|
@ -1,316 +1,286 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useCookies } from 'react-cookie'
|
import { getCookie } from "cookies-next"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from "valtio"
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from "next-i18next"
|
||||||
|
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from "axios"
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from "lodash.debounce"
|
||||||
|
|
||||||
import SummonUnit from '~components/SummonUnit'
|
import SummonUnit from "~components/SummonUnit"
|
||||||
import ExtraSummons from '~components/ExtraSummons'
|
import ExtraSummons from "~components/ExtraSummons"
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from "~utils/api"
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
new: boolean
|
new: boolean
|
||||||
slug?: string
|
summons?: GridSummon[]
|
||||||
createParty: () => Promise<AxiosResponse<any, any>>
|
createParty: () => Promise<AxiosResponse<any, any>>
|
||||||
pushHistory?: (path: string) => void
|
pushHistory?: (path: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonGrid = (props: Props) => {
|
const SummonGrid = (props: Props) => {
|
||||||
// Constants
|
// Constants
|
||||||
const numSummons: number = 4
|
const numSummons: number = 4
|
||||||
|
|
||||||
const { t } = useTranslation('common')
|
// Cookies
|
||||||
|
const cookie = getCookie("account")
|
||||||
|
const accountData: AccountCookie = cookie
|
||||||
|
? JSON.parse(cookie as string)
|
||||||
|
: null
|
||||||
|
const headers = accountData
|
||||||
|
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||||
|
: {}
|
||||||
|
|
||||||
// Cookies
|
// Localization
|
||||||
const [cookies, _] = useCookies(['account'])
|
const { t } = useTranslation("common")
|
||||||
const headers = (cookies.account != null) ? {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
|
||||||
}
|
|
||||||
} : {}
|
|
||||||
|
|
||||||
// Set up state for view management
|
// Set up state for view management
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
const [slug, setSlug] = useState()
|
||||||
|
|
||||||
const [slug, setSlug] = useState()
|
// Create a temporary state to store previous weapon uncap value
|
||||||
const [found, setFound] = useState(false)
|
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||||
const [loading, setLoading] = useState(true)
|
[key: number]: number
|
||||||
const [firstLoadComplete, setFirstLoadComplete] = useState(false)
|
}>({})
|
||||||
|
|
||||||
// Create a temporary state to store previous weapon uncap value
|
// Set the editable flag only on first load
|
||||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
useEffect(() => {
|
||||||
|
// If user is logged in and matches
|
||||||
|
if (
|
||||||
|
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||||
|
props.new
|
||||||
|
)
|
||||||
|
appState.party.editable = true
|
||||||
|
else appState.party.editable = false
|
||||||
|
}, [props.new, accountData, party])
|
||||||
|
|
||||||
// Fetch data from the server
|
// Initialize an array of current uncap values for each summon
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shortcode = (props.slug) ? props.slug : slug
|
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||||
if (shortcode) fetchGrid(shortcode)
|
|
||||||
else appState.party.editable = true
|
|
||||||
}, [slug, props.slug])
|
|
||||||
|
|
||||||
// Set the editable flag only on first load
|
if (appState.grid.summons.mainSummon)
|
||||||
useEffect(() => {
|
initialPreviousUncapValues[-1] =
|
||||||
if (!loading && !firstLoadComplete) {
|
appState.grid.summons.mainSummon.uncap_level
|
||||||
// If user is logged in and matches
|
|
||||||
if ((cookies.account && party.user && cookies.account.user_id === party.user.id) || props.new)
|
|
||||||
appState.party.editable = true
|
|
||||||
else
|
|
||||||
appState.party.editable = false
|
|
||||||
|
|
||||||
setFirstLoadComplete(true)
|
if (appState.grid.summons.friendSummon)
|
||||||
}
|
initialPreviousUncapValues[6] =
|
||||||
}, [props.new, cookies, party, loading, firstLoadComplete])
|
appState.grid.summons.friendSummon.uncap_level
|
||||||
|
|
||||||
// Initialize an array of current uncap values for each summon
|
Object.values(appState.grid.summons.allSummons).map(
|
||||||
useEffect(() => {
|
(o) => (initialPreviousUncapValues[o.position] = o.uncap_level)
|
||||||
let initialPreviousUncapValues: {[key: number]: number} = {}
|
)
|
||||||
|
|
||||||
if (appState.grid.summons.mainSummon)
|
setPreviousUncapValues(initialPreviousUncapValues)
|
||||||
initialPreviousUncapValues[-1] = appState.grid.summons.mainSummon.uncap_level
|
}, [
|
||||||
|
appState.grid.summons.mainSummon,
|
||||||
|
appState.grid.summons.friendSummon,
|
||||||
|
appState.grid.summons.allSummons,
|
||||||
|
])
|
||||||
|
|
||||||
if (appState.grid.summons.friendSummon)
|
// Methods: Adding an object from search
|
||||||
initialPreviousUncapValues[6] = appState.grid.summons.friendSummon.uncap_level
|
function receiveSummonFromSearch(
|
||||||
|
object: Character | Weapon | Summon,
|
||||||
|
position: number
|
||||||
|
) {
|
||||||
|
const summon = object as Summon
|
||||||
|
|
||||||
Object.values(appState.grid.summons.allSummons).map(o => initialPreviousUncapValues[o.position] = o.uncap_level)
|
if (!party.id) {
|
||||||
|
props.createParty().then((response) => {
|
||||||
setPreviousUncapValues(initialPreviousUncapValues)
|
const party = response.data.party
|
||||||
}, [appState.grid.summons.mainSummon, appState.grid.summons.friendSummon, appState.grid.summons.allSummons])
|
|
||||||
|
|
||||||
|
|
||||||
// Methods: Fetching an object from the server
|
|
||||||
async function fetchGrid(shortcode: string) {
|
|
||||||
return api.endpoints.parties.getOneWithObject({ id: shortcode, object: 'summons', params: headers })
|
|
||||||
.then(response => processResult(response))
|
|
||||||
.catch(error => processError(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
function processResult(response: AxiosResponse) {
|
|
||||||
// Store the response
|
|
||||||
const party: Party = response.data.party
|
|
||||||
|
|
||||||
// Store the important party and state-keeping values
|
|
||||||
appState.party.id = party.id
|
appState.party.id = party.id
|
||||||
appState.party.user = party.user
|
setSlug(party.shortcode)
|
||||||
appState.party.favorited = party.favorited
|
|
||||||
appState.party.created_at = party.created_at
|
|
||||||
appState.party.updated_at = party.updated_at
|
|
||||||
|
|
||||||
setFound(true)
|
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
// Populate the weapons in state
|
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||||
populateSummons(party.summons)
|
|
||||||
|
saveSummon(party.id, summon, position).then((response) =>
|
||||||
|
storeGridSummon(response.data.grid_summon)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (party.editable)
|
||||||
|
saveSummon(party.id, summon, position).then((response) =>
|
||||||
|
storeGridSummon(response.data.grid_summon)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function processError(error: any) {
|
async function saveSummon(partyId: string, summon: Summon, position: number) {
|
||||||
if (error.response != null) {
|
let uncapLevel = 3
|
||||||
if (error.response.status == 404) {
|
if (summon.uncap.ulb) uncapLevel = 5
|
||||||
setFound(false)
|
else if (summon.uncap.flb) uncapLevel = 4
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateSummons(list: Array<GridSummon>) {
|
return await api.endpoints.summons.create(
|
||||||
list.forEach((gridObject: GridSummon) => {
|
{
|
||||||
if (gridObject.main)
|
summon: {
|
||||||
appState.grid.summons.mainSummon = gridObject
|
party_id: partyId,
|
||||||
else if (gridObject.friend)
|
summon_id: summon.id,
|
||||||
appState.grid.summons.friendSummon = gridObject
|
position: position,
|
||||||
else if (!gridObject.main && !gridObject.friend && gridObject.position != null)
|
main: position == -1,
|
||||||
appState.grid.summons.allSummons[gridObject.position] = gridObject
|
friend: position == 6,
|
||||||
|
uncap_level: uncapLevel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeGridSummon(gridSummon: GridSummon) {
|
||||||
|
if (gridSummon.position == -1) appState.grid.summons.mainSummon = gridSummon
|
||||||
|
else if (gridSummon.position == 6)
|
||||||
|
appState.grid.summons.friendSummon = gridSummon
|
||||||
|
else appState.grid.summons.allSummons[gridSummon.position] = gridSummon
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Updating uncap level
|
||||||
|
// Note: Saves, but debouncing is not working properly
|
||||||
|
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||||
|
storePreviousUncapValue(position)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (uncapLevel != previousUncapValues[position])
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Adding an object from search
|
function initiateUncapUpdate(
|
||||||
function receiveSummonFromSearch(object: Character | Weapon | Summon, position: number) {
|
id: string,
|
||||||
const summon = object as Summon
|
position: number,
|
||||||
|
uncapLevel: number
|
||||||
|
) {
|
||||||
|
memoizeAction(id, position, uncapLevel)
|
||||||
|
|
||||||
if (!party.id) {
|
// Optimistically update UI
|
||||||
props.createParty()
|
updateUncapLevel(position, uncapLevel)
|
||||||
.then(response => {
|
}
|
||||||
const party = response.data.party
|
|
||||||
appState.party.id = party.id
|
|
||||||
setSlug(party.shortcode)
|
|
||||||
|
|
||||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
const memoizeAction = useCallback(
|
||||||
|
(id: string, position: number, uncapLevel: number) => {
|
||||||
|
debouncedAction(id, position, uncapLevel)
|
||||||
|
},
|
||||||
|
[props, previousUncapValues]
|
||||||
|
)
|
||||||
|
|
||||||
saveSummon(party.id, summon, position)
|
const debouncedAction = useMemo(
|
||||||
.then(response => storeGridSummon(response.data.grid_summon))
|
() =>
|
||||||
})
|
debounce((id, position, number) => {
|
||||||
} else {
|
saveUncap(id, position, number)
|
||||||
if (party.editable)
|
}, 500),
|
||||||
saveSummon(party.id, summon, position)
|
[props, saveUncap]
|
||||||
.then(response => storeGridSummon(response.data.grid_summon))
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveSummon(partyId: string, summon: Summon, position: number) {
|
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||||
let uncapLevel = 3
|
if (appState.grid.summons.mainSummon && position == -1)
|
||||||
if (summon.uncap.ulb) uncapLevel = 5
|
appState.grid.summons.mainSummon.uncap_level = uncapLevel
|
||||||
else if (summon.uncap.flb) uncapLevel = 4
|
else if (appState.grid.summons.friendSummon && position == 6)
|
||||||
|
appState.grid.summons.friendSummon.uncap_level = uncapLevel
|
||||||
return await api.endpoints.summons.create({
|
else appState.grid.summons.allSummons[position].uncap_level = uncapLevel
|
||||||
'summon': {
|
}
|
||||||
'party_id': partyId,
|
|
||||||
'summon_id': summon.id,
|
|
||||||
'position': position,
|
|
||||||
'main': (position == -1),
|
|
||||||
'friend': (position == 6),
|
|
||||||
'uncap_level': uncapLevel
|
|
||||||
}
|
|
||||||
}, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeGridSummon(gridSummon: GridSummon) {
|
function storePreviousUncapValue(position: number) {
|
||||||
if (gridSummon.position == -1)
|
// Save the current value in case of an unexpected result
|
||||||
appState.grid.summons.mainSummon = gridSummon
|
let newPreviousValues = { ...previousUncapValues }
|
||||||
else if (gridSummon.position == 6)
|
|
||||||
appState.grid.summons.friendSummon = gridSummon
|
|
||||||
else
|
|
||||||
appState.grid.summons.allSummons[gridSummon.position] = gridSummon
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods: Updating uncap level
|
if (appState.grid.summons.mainSummon && position == -1)
|
||||||
// Note: Saves, but debouncing is not working properly
|
newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
|
||||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
else if (appState.grid.summons.friendSummon && position == 6)
|
||||||
storePreviousUncapValue(position)
|
newPreviousValues[position] =
|
||||||
|
appState.grid.summons.friendSummon.uncap_level
|
||||||
|
else
|
||||||
|
newPreviousValues[position] =
|
||||||
|
appState.grid.summons.allSummons[position].uncap_level
|
||||||
|
|
||||||
try {
|
setPreviousUncapValues(newPreviousValues)
|
||||||
if (uncapLevel != previousUncapValues[position])
|
}
|
||||||
await api.updateUncap('summon', id, uncapLevel)
|
|
||||||
.then(response => { storeGridSummon(response.data.grid_summon) })
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
// Revert optimistic UI
|
// Render: JSX components
|
||||||
updateUncapLevel(position, previousUncapValues[position])
|
const mainSummonElement = (
|
||||||
|
<div className="LabeledUnit">
|
||||||
|
<div className="Label">{t("summons.main")}</div>
|
||||||
|
<SummonUnit
|
||||||
|
gridSummon={grid.summons.mainSummon}
|
||||||
|
editable={party.editable}
|
||||||
|
key="grid_main_summon"
|
||||||
|
position={-1}
|
||||||
|
unitType={0}
|
||||||
|
updateObject={receiveSummonFromSearch}
|
||||||
|
updateUncap={initiateUncapUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
// Remove optimistic key
|
const friendSummonElement = (
|
||||||
let newPreviousValues = {...previousUncapValues}
|
<div className="LabeledUnit">
|
||||||
delete newPreviousValues[position]
|
<div className="Label">{t("summons.friend")}</div>
|
||||||
setPreviousUncapValues(newPreviousValues)
|
<SummonUnit
|
||||||
}
|
gridSummon={grid.summons.friendSummon}
|
||||||
}
|
editable={party.editable}
|
||||||
|
key="grid_friend_summon"
|
||||||
function initiateUncapUpdate(id: string, position: number, uncapLevel: number) {
|
position={6}
|
||||||
memoizeAction(id, position, uncapLevel)
|
unitType={2}
|
||||||
|
updateObject={receiveSummonFromSearch}
|
||||||
// Optimistically update UI
|
updateUncap={initiateUncapUpdate}
|
||||||
updateUncapLevel(position, uncapLevel)
|
/>
|
||||||
}
|
</div>
|
||||||
|
)
|
||||||
const memoizeAction = useCallback(
|
const summonGridElement = (
|
||||||
(id: string, position: number, uncapLevel: number) => {
|
<div id="LabeledGrid">
|
||||||
debouncedAction(id, position, uncapLevel)
|
<div className="Label">{t("summons.summons")}</div>
|
||||||
}, [props, previousUncapValues]
|
<ul id="grid_summons">
|
||||||
)
|
{Array.from(Array(numSummons)).map((x, i) => {
|
||||||
|
return (
|
||||||
const debouncedAction = useMemo(() =>
|
<li key={`grid_unit_${i}`}>
|
||||||
debounce((id, position, number) => {
|
<SummonUnit
|
||||||
saveUncap(id, position, number)
|
gridSummon={grid.summons.allSummons[i]}
|
||||||
}, 500), [props, saveUncap]
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
|
||||||
if (appState.grid.summons.mainSummon && position == -1)
|
|
||||||
appState.grid.summons.mainSummon.uncap_level = uncapLevel
|
|
||||||
else if (appState.grid.summons.friendSummon && position == 6)
|
|
||||||
appState.grid.summons.friendSummon.uncap_level = uncapLevel
|
|
||||||
else
|
|
||||||
appState.grid.summons.allSummons[position].uncap_level = uncapLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
function storePreviousUncapValue(position: number) {
|
|
||||||
// Save the current value in case of an unexpected result
|
|
||||||
let newPreviousValues = {...previousUncapValues}
|
|
||||||
|
|
||||||
if (appState.grid.summons.mainSummon && position == -1) newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
|
|
||||||
else if (appState.grid.summons.friendSummon && position == 6) newPreviousValues[position] = appState.grid.summons.friendSummon.uncap_level
|
|
||||||
else newPreviousValues[position] = appState.grid.summons.allSummons[position].uncap_level
|
|
||||||
|
|
||||||
setPreviousUncapValues(newPreviousValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render: JSX components
|
|
||||||
const mainSummonElement = (
|
|
||||||
<div className="LabeledUnit">
|
|
||||||
<div className="Label">{t('summons.main')}</div>
|
|
||||||
<SummonUnit
|
|
||||||
gridSummon={grid.summons.mainSummon}
|
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
key="grid_main_summon"
|
position={i}
|
||||||
position={-1}
|
unitType={1}
|
||||||
unitType={0}
|
|
||||||
updateObject={receiveSummonFromSearch}
|
updateObject={receiveSummonFromSearch}
|
||||||
updateUncap={initiateUncapUpdate}
|
updateUncap={initiateUncapUpdate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</li>
|
||||||
)
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
const subAuraSummonElement = (
|
||||||
|
<ExtraSummons
|
||||||
|
grid={grid.summons.allSummons}
|
||||||
|
editable={party.editable}
|
||||||
|
exists={false}
|
||||||
|
offset={numSummons}
|
||||||
|
updateObject={receiveSummonFromSearch}
|
||||||
|
updateUncap={initiateUncapUpdate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div id="SummonGrid">
|
||||||
|
{mainSummonElement}
|
||||||
|
{friendSummonElement}
|
||||||
|
{summonGridElement}
|
||||||
|
</div>
|
||||||
|
|
||||||
const friendSummonElement = (
|
{subAuraSummonElement}
|
||||||
<div className="LabeledUnit">
|
</div>
|
||||||
<div className="Label">{t('summons.friend')}</div>
|
)
|
||||||
<SummonUnit
|
|
||||||
gridSummon={grid.summons.friendSummon}
|
|
||||||
editable={party.editable}
|
|
||||||
key="grid_friend_summon"
|
|
||||||
position={6}
|
|
||||||
unitType={2}
|
|
||||||
updateObject={receiveSummonFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
const summonGridElement = (
|
|
||||||
<div id="LabeledGrid">
|
|
||||||
<div className="Label">{t('summons.summons')}</div>
|
|
||||||
<ul id="grid_summons">
|
|
||||||
{Array.from(Array(numSummons)).map((x, i) => {
|
|
||||||
return (<li key={`grid_unit_${i}`} >
|
|
||||||
<SummonUnit
|
|
||||||
gridSummon={grid.summons.allSummons[i]}
|
|
||||||
editable={party.editable}
|
|
||||||
position={i}
|
|
||||||
unitType={1}
|
|
||||||
updateObject={receiveSummonFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
</li>)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
const subAuraSummonElement = (
|
|
||||||
<ExtraSummons
|
|
||||||
grid={grid.summons.allSummons}
|
|
||||||
editable={party.editable}
|
|
||||||
exists={false}
|
|
||||||
offset={numSummons}
|
|
||||||
updateObject={receiveSummonFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div id="SummonGrid">
|
|
||||||
{ mainSummonElement }
|
|
||||||
{ friendSummonElement }
|
|
||||||
{ summonGridElement }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ subAuraSummonElement }
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonGrid
|
export default SummonGrid
|
||||||
|
|
|
||||||
|
|
@ -1,286 +1,246 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useCookies } from 'react-cookie'
|
import { getCookie } from "cookies-next"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from "valtio"
|
||||||
|
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from "axios"
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from "lodash.debounce"
|
||||||
|
|
||||||
import WeaponUnit from '~components/WeaponUnit'
|
import WeaponUnit from "~components/WeaponUnit"
|
||||||
import ExtraWeapons from '~components/ExtraWeapons'
|
import ExtraWeapons from "~components/ExtraWeapons"
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from "~utils/api"
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
new: boolean
|
new: boolean
|
||||||
slug?: string
|
weapons?: GridWeapon[]
|
||||||
createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>
|
createParty: (extra: boolean) => Promise<AxiosResponse<any, any>>
|
||||||
pushHistory?: (path: string) => void
|
pushHistory?: (path: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponGrid = (props: Props) => {
|
const WeaponGrid = (props: Props) => {
|
||||||
// Constants
|
// Constants
|
||||||
const numWeapons: number = 9
|
const numWeapons: number = 9
|
||||||
|
|
||||||
// Cookies
|
// Cookies
|
||||||
const [cookies] = useCookies(['account'])
|
const cookie = getCookie("account")
|
||||||
const headers = (cookies.account != null) ? {
|
const accountData: AccountCookie = cookie
|
||||||
headers: {
|
? JSON.parse(cookie as string)
|
||||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
: null
|
||||||
}
|
const headers = accountData
|
||||||
} : {}
|
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||||
|
: {}
|
||||||
|
|
||||||
// Set up state for view management
|
// Set up state for view management
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
const [slug, setSlug] = useState()
|
||||||
|
|
||||||
const [slug, setSlug] = useState()
|
// Create a temporary state to store previous weapon uncap values
|
||||||
const [found, setFound] = useState(false)
|
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||||
const [loading, setLoading] = useState(true)
|
[key: number]: number
|
||||||
const [firstLoadComplete, setFirstLoadComplete] = useState(false)
|
}>({})
|
||||||
|
|
||||||
// Create a temporary state to store previous weapon uncap values
|
// Set the editable flag only on first load
|
||||||
const [previousUncapValues, setPreviousUncapValues] = useState<{[key: number]: number}>({})
|
useEffect(() => {
|
||||||
|
// If user is logged in and matches
|
||||||
|
if (
|
||||||
|
(accountData && party.user && accountData.userId === party.user.id) ||
|
||||||
|
props.new
|
||||||
|
)
|
||||||
|
appState.party.editable = true
|
||||||
|
else appState.party.editable = false
|
||||||
|
}, [props.new, accountData, party])
|
||||||
|
|
||||||
// Fetch data from the server
|
// Initialize an array of current uncap values for each weapon
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shortcode = (props.slug) ? props.slug : slug
|
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||||
if (shortcode) fetchGrid(shortcode)
|
|
||||||
else appState.party.editable = true
|
|
||||||
}, [slug, props.slug])
|
|
||||||
|
|
||||||
// Set the editable flag only on first load
|
if (appState.grid.weapons.mainWeapon)
|
||||||
useEffect(() => {
|
initialPreviousUncapValues[-1] =
|
||||||
if (!loading && !firstLoadComplete) {
|
appState.grid.weapons.mainWeapon.uncap_level
|
||||||
// If user is logged in and matches
|
|
||||||
if ((cookies.account && party.user && cookies.account.user_id === party.user.id) || props.new)
|
|
||||||
appState.party.editable = true
|
|
||||||
else
|
|
||||||
appState.party.editable = false
|
|
||||||
|
|
||||||
setFirstLoadComplete(true)
|
Object.values(appState.grid.weapons.allWeapons).map(
|
||||||
}
|
(o) => (initialPreviousUncapValues[o.position] = o.uncap_level)
|
||||||
}, [props.new, cookies, party, loading, firstLoadComplete])
|
)
|
||||||
|
|
||||||
// Initialize an array of current uncap values for each weapon
|
setPreviousUncapValues(initialPreviousUncapValues)
|
||||||
useEffect(() => {
|
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
|
||||||
let initialPreviousUncapValues: {[key: number]: number} = {}
|
|
||||||
|
|
||||||
if (appState.grid.weapons.mainWeapon)
|
// Methods: Adding an object from search
|
||||||
initialPreviousUncapValues[-1] = appState.grid.weapons.mainWeapon.uncap_level
|
function receiveWeaponFromSearch(
|
||||||
|
object: Character | Weapon | Summon,
|
||||||
|
position: number
|
||||||
|
) {
|
||||||
|
const weapon = object as Weapon
|
||||||
|
if (position == 1) appState.party.element = weapon.element
|
||||||
|
|
||||||
Object.values(appState.grid.weapons.allWeapons).map(o => initialPreviousUncapValues[o.position] = o.uncap_level)
|
if (!party.id) {
|
||||||
|
props.createParty(party.extra).then((response) => {
|
||||||
setPreviousUncapValues(initialPreviousUncapValues)
|
const party = response.data.party
|
||||||
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
|
|
||||||
|
|
||||||
// Methods: Fetching an object from the server
|
|
||||||
async function fetchGrid(shortcode: string) {
|
|
||||||
return api.endpoints.parties.getOneWithObject({ id: shortcode, object: 'weapons', params: headers })
|
|
||||||
.then(response => processResult(response))
|
|
||||||
.catch(error => processError(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
function processResult(response: AxiosResponse) {
|
|
||||||
// Store the response
|
|
||||||
const party: Party = response.data.party
|
|
||||||
|
|
||||||
// Store the important party and state-keeping values
|
|
||||||
appState.party.id = party.id
|
appState.party.id = party.id
|
||||||
appState.party.extra = party.extra
|
setSlug(party.shortcode)
|
||||||
appState.party.user = party.user
|
|
||||||
appState.party.favorited = party.favorited
|
|
||||||
appState.party.created_at = party.created_at
|
|
||||||
appState.party.updated_at = party.updated_at
|
|
||||||
|
|
||||||
setFound(true)
|
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
// Populate the weapons in state
|
saveWeapon(party.id, weapon, position).then((response) =>
|
||||||
populateWeapons(party.weapons)
|
storeGridWeapon(response.data.grid_weapon)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
saveWeapon(party.id, weapon, position).then((response) =>
|
||||||
|
storeGridWeapon(response.data.grid_weapon)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function processError(error: any) {
|
async function saveWeapon(partyId: string, weapon: Weapon, position: number) {
|
||||||
if (error.response != null) {
|
let uncapLevel = 3
|
||||||
if (error.response.status == 404) {
|
if (weapon.uncap.ulb) uncapLevel = 5
|
||||||
setFound(false)
|
else if (weapon.uncap.flb) uncapLevel = 4
|
||||||
setLoading(false)
|
|
||||||
}
|
return await api.endpoints.weapons.create(
|
||||||
} else {
|
{
|
||||||
console.error(error)
|
weapon: {
|
||||||
}
|
party_id: partyId,
|
||||||
|
weapon_id: weapon.id,
|
||||||
|
position: position,
|
||||||
|
mainhand: position == -1,
|
||||||
|
uncap_level: uncapLevel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeGridWeapon(gridWeapon: GridWeapon) {
|
||||||
|
if (gridWeapon.position == -1) {
|
||||||
|
appState.grid.weapons.mainWeapon = gridWeapon
|
||||||
|
appState.party.element = gridWeapon.object.element
|
||||||
|
} else {
|
||||||
|
// Store the grid unit at the correct position
|
||||||
|
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function populateWeapons(list: Array<GridWeapon>) {
|
// Methods: Updating uncap level
|
||||||
list.forEach((gridObject: GridWeapon) => {
|
// Note: Saves, but debouncing is not working properly
|
||||||
if (gridObject.mainhand) {
|
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||||
appState.grid.weapons.mainWeapon = gridObject
|
storePreviousUncapValue(position)
|
||||||
appState.party.element = gridObject.object.element
|
|
||||||
} else if (!gridObject.mainhand && gridObject.position != null) {
|
try {
|
||||||
appState.grid.weapons.allWeapons[gridObject.position] = gridObject
|
if (uncapLevel != previousUncapValues[position])
|
||||||
}
|
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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Methods: Adding an object from search
|
|
||||||
function receiveWeaponFromSearch(object: Character | Weapon | Summon, position: number) {
|
|
||||||
const weapon = object as Weapon
|
|
||||||
if (position == 1)
|
|
||||||
appState.party.element = weapon.element
|
|
||||||
|
|
||||||
if (!party.id) {
|
function initiateUncapUpdate(
|
||||||
props.createParty(party.extra)
|
id: string,
|
||||||
.then(response => {
|
position: number,
|
||||||
const party = response.data.party
|
uncapLevel: number
|
||||||
appState.party.id = party.id
|
) {
|
||||||
setSlug(party.shortcode)
|
memoizeAction(id, position, uncapLevel)
|
||||||
|
|
||||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
// Optimistically update UI
|
||||||
|
updateUncapLevel(position, uncapLevel)
|
||||||
|
}
|
||||||
|
|
||||||
saveWeapon(party.id, weapon, position)
|
const memoizeAction = useCallback(
|
||||||
.then(response => storeGridWeapon(response.data.grid_weapon))
|
(id: string, position: number, uncapLevel: number) => {
|
||||||
})
|
debouncedAction(id, position, uncapLevel)
|
||||||
} else {
|
},
|
||||||
saveWeapon(party.id, weapon, position)
|
[props, previousUncapValues]
|
||||||
.then(response => storeGridWeapon(response.data.grid_weapon))
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveWeapon(partyId: string, weapon: Weapon, position: number) {
|
const debouncedAction = useMemo(
|
||||||
let uncapLevel = 3
|
() =>
|
||||||
if (weapon.uncap.ulb) uncapLevel = 5
|
debounce((id, position, number) => {
|
||||||
else if (weapon.uncap.flb) uncapLevel = 4
|
saveUncap(id, position, number)
|
||||||
|
}, 500),
|
||||||
return await api.endpoints.weapons.create({
|
[props, saveUncap]
|
||||||
'weapon': {
|
)
|
||||||
'party_id': partyId,
|
|
||||||
'weapon_id': weapon.id,
|
|
||||||
'position': position,
|
|
||||||
'mainhand': (position == -1),
|
|
||||||
'uncap_level': uncapLevel
|
|
||||||
}
|
|
||||||
}, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeGridWeapon(gridWeapon: GridWeapon) {
|
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||||
if (gridWeapon.position == -1) {
|
if (appState.grid.weapons.mainWeapon && position == -1)
|
||||||
appState.grid.weapons.mainWeapon = gridWeapon
|
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
||||||
appState.party.element = gridWeapon.object.element
|
else appState.grid.weapons.allWeapons[position].uncap_level = uncapLevel
|
||||||
} else {
|
}
|
||||||
// Store the grid unit at the correct position
|
|
||||||
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods: Updating uncap level
|
function storePreviousUncapValue(position: number) {
|
||||||
// Note: Saves, but debouncing is not working properly
|
// Save the current value in case of an unexpected result
|
||||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
let newPreviousValues = { ...previousUncapValues }
|
||||||
storePreviousUncapValue(position)
|
newPreviousValues[position] =
|
||||||
|
appState.grid.weapons.mainWeapon && position == -1
|
||||||
|
? appState.grid.weapons.mainWeapon.uncap_level
|
||||||
|
: appState.grid.weapons.allWeapons[position].uncap_level
|
||||||
|
setPreviousUncapValues(newPreviousValues)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
// Render: JSX components
|
||||||
if (uncapLevel != previousUncapValues[position])
|
const mainhandElement = (
|
||||||
await api.updateUncap('weapon', id, uncapLevel)
|
<WeaponUnit
|
||||||
.then(response => { storeGridWeapon(response.data.grid_weapon) })
|
gridWeapon={appState.grid.weapons.mainWeapon}
|
||||||
} catch (error) {
|
editable={party.editable}
|
||||||
console.error(error)
|
key="grid_mainhand"
|
||||||
|
position={-1}
|
||||||
|
unitType={0}
|
||||||
|
updateObject={receiveWeaponFromSearch}
|
||||||
|
updateUncap={initiateUncapUpdate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
// Revert optimistic UI
|
const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => {
|
||||||
updateUncapLevel(position, previousUncapValues[position])
|
|
||||||
|
|
||||||
// Remove optimistic key
|
|
||||||
let newPreviousValues = {...previousUncapValues}
|
|
||||||
delete newPreviousValues[position]
|
|
||||||
setPreviousUncapValues(newPreviousValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initiateUncapUpdate(id: string, position: number, uncapLevel: number) {
|
|
||||||
memoizeAction(id, position, uncapLevel)
|
|
||||||
|
|
||||||
// Optimistically update UI
|
|
||||||
updateUncapLevel(position, uncapLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
const memoizeAction = useCallback(
|
|
||||||
(id: string, position: number, uncapLevel: number) => {
|
|
||||||
debouncedAction(id, position, uncapLevel)
|
|
||||||
}, [props, previousUncapValues]
|
|
||||||
)
|
|
||||||
|
|
||||||
const debouncedAction = useMemo(() =>
|
|
||||||
debounce((id, position, number) => {
|
|
||||||
saveUncap(id, position, number)
|
|
||||||
}, 500), [props, saveUncap]
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
|
||||||
if (appState.grid.weapons.mainWeapon && position == -1)
|
|
||||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
|
||||||
else
|
|
||||||
appState.grid.weapons.allWeapons[position].uncap_level = uncapLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
function storePreviousUncapValue(position: number) {
|
|
||||||
// Save the current value in case of an unexpected result
|
|
||||||
let newPreviousValues = {...previousUncapValues}
|
|
||||||
newPreviousValues[position] = (appState.grid.weapons.mainWeapon && position == -1) ?
|
|
||||||
appState.grid.weapons.mainWeapon.uncap_level : appState.grid.weapons.allWeapons[position].uncap_level
|
|
||||||
setPreviousUncapValues(newPreviousValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render: JSX components
|
|
||||||
const mainhandElement = (
|
|
||||||
<WeaponUnit
|
|
||||||
gridWeapon={appState.grid.weapons.mainWeapon}
|
|
||||||
editable={party.editable}
|
|
||||||
key="grid_mainhand"
|
|
||||||
position={-1}
|
|
||||||
unitType={0}
|
|
||||||
updateObject={receiveWeaponFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const weaponGridElement = (
|
|
||||||
Array.from(Array(numWeapons)).map((x, i) => {
|
|
||||||
return (
|
|
||||||
<li key={`grid_unit_${i}`} >
|
|
||||||
<WeaponUnit
|
|
||||||
gridWeapon={appState.grid.weapons.allWeapons[i]}
|
|
||||||
editable={party.editable}
|
|
||||||
position={i}
|
|
||||||
unitType={1}
|
|
||||||
updateObject={receiveWeaponFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const extraGridElement = (
|
|
||||||
<ExtraWeapons
|
|
||||||
grid={appState.grid.weapons.allWeapons}
|
|
||||||
editable={party.editable}
|
|
||||||
offset={numWeapons}
|
|
||||||
updateObject={receiveWeaponFromSearch}
|
|
||||||
updateUncap={initiateUncapUpdate}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="WeaponGrid">
|
<li key={`grid_unit_${i}`}>
|
||||||
<div id="MainGrid">
|
<WeaponUnit
|
||||||
{ mainhandElement }
|
gridWeapon={appState.grid.weapons.allWeapons[i]}
|
||||||
<ul className="grid_weapons">{ weaponGridElement }</ul>
|
editable={party.editable}
|
||||||
</div>
|
position={i}
|
||||||
|
unitType={1}
|
||||||
{ (() => { return (party.extra) ? extraGridElement : '' })() }
|
updateObject={receiveWeaponFromSearch}
|
||||||
</div>
|
updateUncap={initiateUncapUpdate}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const extraGridElement = (
|
||||||
|
<ExtraWeapons
|
||||||
|
grid={appState.grid.weapons.allWeapons}
|
||||||
|
editable={party.editable}
|
||||||
|
offset={numWeapons}
|
||||||
|
updateObject={receiveWeaponFromSearch}
|
||||||
|
updateUncap={initiateUncapUpdate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="WeaponGrid">
|
||||||
|
<div id="MainGrid">
|
||||||
|
{mainhandElement}
|
||||||
|
<ul className="grid_weapons">{weaponGridElement}</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
return party.extra ? extraGridElement : ""
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponGrid
|
export default WeaponGrid
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue