hensei-web/components/weapon/WeaponModal/index.tsx
Justin Edmund 103ef7e1a2
Quality pass (#326)
* Move min-width to RaidCombobox, not Popover

This fixes #318

* Use snapshots to make tokens reactive

This fixes #319

* Revert ChatGPT refactor of this method

Oops. This code was nice, but it didn't actually assign `false` to keys to be sent to the server. We will revisit this, but it needs to be fixed right now.

This fixes #325

* Ignore gacha directory

We will probably scrape these images soon.

* Add translation for Auto Summon token

* Add auto summon token to app state

* Set battle settings in state on update

Also renames PartyDetails to PartyFooter and makes description reactive

* Stop 1password icon from appearing in name field

* Use snapshot for reactive Edit party modal

* Fix Edit modal placeholder colors

* Fix bug with RaidCombobox and Farming

Selecting farming then opening the raid combobox *twice* consecutively would put you in no segment, so no raids appeared

Fixes #323

* Fix values staying in Edit team even if not saved

The values a user entered in the Edit team modal would persist even if the user hit cancel to close the modal. They wouldn't save to the server, but very confusing nonetheless. Now fixed.

* Fix unreadable colors in ElementToggle

* Fix button alignment in weapon modal

* Add text to filters button on small screens

The FilterBar showed a left aligned filter icon on mobile for months and it was driving me insane

* Remove extraneous code from Header

Including the party name, since it's at the top now

* Fix Alert at small sizes

* Make copy link toast work again

* Remove stylesheet links

* Fix remix toasts and alerts from both locations

The remix toast and alert was barely hooked up and not showing up when invoked from PartyHeader.

It now shows up whether you remix your own team (from PartyDropdown) or if you remix another person's team (from PartyHeader).
2023-06-21 03:39:25 -07:00

411 lines
12 KiB
TypeScript

import React, { PropsWithChildren, useEffect, useState } from 'react'
import { getCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import { AxiosResponse } from 'axios'
import {
Dialog,
DialogClose,
DialogTitle,
DialogTrigger,
} from '~components/common/Dialog'
import DialogContent from '~components/common/DialogContent'
import AwakeningSelectWithInput from '~components/mastery/AwakeningSelectWithInput'
import AXSelect from '~components/mastery/AxSelect'
import ElementToggle from '~components/ElementToggle'
import WeaponKeySelect from '~components/weapon/WeaponKeySelect'
import Button from '~components/common/Button'
import api from '~utils/api'
import { appState } from '~utils/appState'
import { NO_AWAKENING } from '~data/awakening'
import CrossIcon from '~public/icons/Cross.svg'
import './index.scss'
interface GridWeaponObject {
weapon: {
element?: number
weapon_key1_id?: string
weapon_key2_id?: string
weapon_key3_id?: string
ax_modifier1?: number
ax_modifier2?: number
ax_strength1?: number
ax_strength2?: number
awakening_id?: string
awakening_level?: Number
}
}
interface Props {
gridWeapon: GridWeapon
open: boolean
onOpenChange: (open: boolean) => void
}
const WeaponModal = ({
gridWeapon,
open: modalOpen,
children,
onOpenChange,
}: PropsWithChildren<Props>) => {
const router = useRouter()
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const { t } = useTranslation('common')
// Cookies
const cookie = getCookie('account')
const accountData: AccountCookie = cookie
? JSON.parse(cookie as string)
: null
const headers = accountData
? { Authorization: `Bearer ${accountData.token}` }
: {}
// State
const [open, setOpen] = useState(false)
const [formValid, setFormValid] = useState(false)
const [element, setElement] = useState(-1)
const [awakening, setAwakening] = useState<Awakening>()
const [awakeningLevel, setAwakeningLevel] = useState(1)
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
const [weaponKey1, setWeaponKey1] = useState<WeaponKey | undefined>()
const [weaponKey2, setWeaponKey2] = useState<WeaponKey | undefined>()
const [weaponKey3, setWeaponKey3] = useState<WeaponKey | undefined>()
const [weaponKey1Id, setWeaponKey1Id] = useState('')
const [weaponKey2Id, setWeaponKey2Id] = useState('')
const [weaponKey3Id, setWeaponKey3Id] = useState('')
const [weaponKey1Open, setWeaponKey1Open] = useState(false)
const [weaponKey2Open, setWeaponKey2Open] = useState(false)
const [weaponKey3Open, setWeaponKey3Open] = useState(false)
const [weaponKey4Open, setWeaponKey4Open] = useState(false)
const [ax1Open, setAx1Open] = useState(false)
const [ax2Open, setAx2Open] = useState(false)
const [awakeningOpen, setAwakeningOpen] = useState(false)
// Refs
const headerRef = React.createRef<HTMLDivElement>()
const footerRef = React.createRef<HTMLDivElement>()
// Hooks
useEffect(() => {
setOpen(modalOpen)
handleOpenChange(modalOpen)
}, [modalOpen])
useEffect(() => {
setElement(gridWeapon.element)
if (gridWeapon.weapon_keys) {
gridWeapon.weapon_keys.forEach((key) => {
if (key.slot + 1 === 1) {
setWeaponKey1(key)
} else if (key.slot + 1 === 2) {
setWeaponKey2(key)
} else if (key.slot + 1 === 3) {
setWeaponKey3(key)
}
})
}
}, [gridWeapon])
function receiveAxValues(
primaryAxModifier: number,
primaryAxValue: number,
secondaryAxModifier: number,
secondaryAxValue: number
) {
setPrimaryAxModifier(primaryAxModifier)
setSecondaryAxModifier(secondaryAxModifier)
setPrimaryAxValue(primaryAxValue)
setSecondaryAxValue(secondaryAxValue)
}
function receiveValidity(isValid: boolean) {
setFormValid(isValid)
}
function receiveAwakeningValues(id: string, level: number) {
setAwakening(gridWeapon.object.awakenings.find((a) => a.id === id))
setAwakeningLevel(level)
setFormValid(true)
}
function receiveElementValue(element: string) {
setElement(parseInt(element))
}
function prepareObject() {
let object: GridWeaponObject = { weapon: {} }
if (gridWeapon.object.element == 0) object.weapon.element = element
if ([2, 3, 17, 24].includes(gridWeapon.object.series) && weaponKey1Id) {
object.weapon.weapon_key1_id = weaponKey1Id
}
if ([2, 3, 17].includes(gridWeapon.object.series) && weaponKey2Id)
object.weapon.weapon_key2_id = weaponKey2Id
if (gridWeapon.object.series == 17 && weaponKey3Id)
object.weapon.weapon_key3_id = weaponKey3Id
if (gridWeapon.object.ax && gridWeapon.object.ax_type > 0) {
object.weapon.ax_modifier1 = primaryAxModifier
object.weapon.ax_modifier2 = secondaryAxModifier
object.weapon.ax_strength1 = primaryAxValue
object.weapon.ax_strength2 = secondaryAxValue
}
if (gridWeapon.object.awakenings) {
object.weapon.awakening_id = awakening?.id
object.weapon.awakening_level = awakeningLevel
}
return object
}
async function updateWeapon() {
const updateObject = prepareObject()
return await api.endpoints.grid_weapons
.update(gridWeapon.id, updateObject, headers)
.then((response) => processResult(response))
.catch((error) => processError(error))
}
function processResult(response: AxiosResponse) {
const gridWeapon: GridWeapon = response.data
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
if (onOpenChange) onOpenChange(false)
setOpen(false)
}
function processError(error: any) {
console.error(error)
}
function receiveWeaponKey(value: string, slot: number) {
if (slot === 0) setWeaponKey1Id(value)
if (slot === 1) setWeaponKey2Id(value)
if (slot === 2) setWeaponKey3Id(value)
}
const elementSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.element')}</h3>
<ElementToggle
currentElement={element}
sendValue={receiveElementValue}
/>
</section>
)
}
function openSelect(index: 1 | 2 | 3 | 4) {
setWeaponKey1Open(index === 1 ? !weaponKey1Open : false)
setWeaponKey2Open(index === 2 ? !weaponKey2Open : false)
setWeaponKey3Open(index === 3 ? !weaponKey3Open : false)
setWeaponKey4Open(index === 4 ? !weaponKey4Open : false)
}
function receiveAxOpen(index: 1 | 2, isOpen: boolean) {
if (index === 1) setAx1Open(isOpen)
if (index === 2) setAx2Open(isOpen)
}
function receiveAwakeningOpen(isOpen: boolean) {
setAwakeningOpen(isOpen)
}
const keySelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
{[2, 3, 17, 22].includes(gridWeapon.object.series) ? (
<WeaponKeySelect
open={weaponKey1Open}
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
series={gridWeapon.object.series}
slot={0}
onOpenChange={() => openSelect(1)}
onChange={receiveWeaponKey}
onClose={() => setWeaponKey1Open(false)}
/>
) : (
''
)}
{[2, 3, 17].includes(gridWeapon.object.series) ? (
<WeaponKeySelect
open={weaponKey2Open}
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
series={gridWeapon.object.series}
slot={1}
onOpenChange={() => openSelect(2)}
onChange={receiveWeaponKey}
onClose={() => setWeaponKey2Open(false)}
/>
) : (
''
)}
{gridWeapon.object.series == 17 ? (
<WeaponKeySelect
open={weaponKey3Open}
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
series={gridWeapon.object.series}
slot={2}
onOpenChange={() => openSelect(3)}
onChange={receiveWeaponKey}
onClose={() => setWeaponKey3Open(false)}
/>
) : (
''
)}
{gridWeapon.object.series == 24 && gridWeapon.object.uncap.ulb ? (
<WeaponKeySelect
open={weaponKey4Open}
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
series={gridWeapon.object.series}
slot={0}
onOpenChange={() => openSelect(4)}
onChange={receiveWeaponKey}
onClose={() => setWeaponKey4Open(false)}
/>
) : (
''
)}
</section>
)
}
const axSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
<AXSelect
axType={gridWeapon.object.ax_type}
currentSkills={gridWeapon.ax}
onOpenChange={receiveAxOpen}
sendValidity={receiveValidity}
sendValues={receiveAxValues}
/>
</section>
)
}
const awakeningSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.awakening')}</h3>
<AwakeningSelectWithInput
dataSet={gridWeapon.object.awakenings}
awakening={gridWeapon.awakening?.type}
level={gridWeapon.awakening?.level}
defaultAwakening={NO_AWAKENING}
maxLevel={gridWeapon.object.max_awakening_level}
onOpenChange={receiveAwakeningOpen}
sendValidity={receiveValidity}
sendValues={receiveAwakeningValues}
/>
</section>
)
}
function handleOpenChange(open: boolean) {
if (gridWeapon.object.ax || gridWeapon.object.awakenings) {
setFormValid(false)
} else {
setFormValid(true)
}
setOpen(open)
onOpenChange(open)
}
const anySelectOpen =
weaponKey1Open ||
weaponKey2Open ||
weaponKey3Open ||
weaponKey4Open ||
ax1Open ||
ax2Open ||
awakeningOpen
function onEscapeKeyDown(event: KeyboardEvent) {
if (anySelectOpen) {
return event.preventDefault()
} else {
setOpen(false)
}
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent
className="Weapon"
headerref={headerRef}
footerref={footerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={onEscapeKeyDown}
>
<div className="DialogHeader Short" ref={headerRef}>
<img
alt={gridWeapon.object.name[locale]}
className="DialogImage"
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${gridWeapon.object.granblue_id}.jpg`}
/>
<div className="DialogTop">
<DialogTitle className="SubTitle">
{t('modals.weapon.title')}
</DialogTitle>
<DialogTitle className="DialogTitle">
{gridWeapon.object.name[locale]}
</DialogTitle>
</div>
<DialogClose className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</DialogClose>
</div>
<div className="mods">
{gridWeapon.object.element == 0 ? elementSelect() : ''}
{[2, 3, 17, 24].includes(gridWeapon.object.series) ? keySelect() : ''}
{gridWeapon.object.ax ? axSelect() : ''}
{gridWeapon.object.awakenings ? awakeningSelect() : ''}
</div>
<div className="DialogFooter" ref={footerRef}>
<div className="actions">
<Button
contained={true}
onClick={updateWeapon}
disabled={!formValid}
text={t('modals.weapon.buttons.confirm')}
/>
</div>
</div>
</DialogContent>
</Dialog>
)
}
export default WeaponModal