Implement CharacterModal
Need sub-components
This commit is contained in:
parent
6cd55e9166
commit
61c7341ee3
2 changed files with 351 additions and 0 deletions
41
components/CharacterModal/index.scss
Normal file
41
components/CharacterModal/index.scss
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
.Character.Dialog {
|
||||
min-width: 480px;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
min-width: inherit;
|
||||
}
|
||||
|
||||
.mods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-4x;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
|
||||
h3 {
|
||||
color: $grey-55;
|
||||
font-size: $font-small;
|
||||
margin-bottom: $unit;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: $grey-90;
|
||||
}
|
||||
}
|
||||
|
||||
.Button {
|
||||
font-size: $font-regular;
|
||||
padding: ($unit * 1.5) ($unit-2x);
|
||||
width: 100%;
|
||||
|
||||
&.btn-disabled {
|
||||
background: $grey-90;
|
||||
color: $grey-70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
310
components/CharacterModal/index.tsx
Normal file
310
components/CharacterModal/index.tsx
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
// Core dependencies
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { getCookie } from 'cookies-next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
// UI dependencies
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '~components/Dialog'
|
||||
import Button from '~components/Button'
|
||||
import AwakeningSelect from '~components/AwakeningSelect'
|
||||
|
||||
// Utilities
|
||||
import api from '~utils/api'
|
||||
import { appState } from '~utils/appState'
|
||||
import { retrieveCookies } from '~utils/retrieveCookies'
|
||||
|
||||
// Styles and icons
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import './index.scss'
|
||||
|
||||
// Types
|
||||
interface GridCharacterObject {
|
||||
character: {
|
||||
ring_modifier1: number
|
||||
ring_modifier2: number
|
||||
ring_modifier3: number
|
||||
ring_modifier4: number
|
||||
ring_strength1: number
|
||||
ring_strength2: number
|
||||
ring_strength3: number
|
||||
ring_strength4: number
|
||||
awakening_type: number
|
||||
awakening_level: number
|
||||
transcendence_step: number
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
gridCharacter: GridCharacter
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const CharacterModal = ({ gridCharacter, children }: Props) => {
|
||||
const router = useRouter()
|
||||
const locale =
|
||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Cookies
|
||||
const cookies = retrieveCookies()
|
||||
|
||||
// UI state
|
||||
const [open, setOpen] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
const [ring1Open, setRing1Open] = useState(false)
|
||||
const [ring2Open, setRing2Open] = useState(false)
|
||||
const [ring3Open, setRing3Open] = useState(false)
|
||||
const [ring4Open, setRing4Open] = useState(false)
|
||||
const [earringOpen, setEarringOpen] = useState(false)
|
||||
const [awakeningOpen, setAwakeningOpen] = useState(false)
|
||||
|
||||
// Character properties: Ring
|
||||
const [ringModifier1, setRingModifier1] = useState(0)
|
||||
const [ringModifier2, setRingModifier2] = useState(0)
|
||||
const [ringModifier3, setRingModifier3] = useState(0)
|
||||
const [ringModifier4, setRingModifier4] = useState(0)
|
||||
|
||||
const [ringStrength1, setRingStrength1] = useState(0)
|
||||
const [ringStrength2, setRingStrength2] = useState(0)
|
||||
const [ringStrength3, setRingStrength3] = useState(0)
|
||||
const [ringStrength4, setRingStrength4] = useState(0)
|
||||
|
||||
// Character properties: Earrings
|
||||
const [earringModifier, setEarringModifier] = useState(0)
|
||||
const [earringStrength, setEarringStrength] = useState(0)
|
||||
|
||||
// Character properties: Awakening
|
||||
const [awakeningType, setAwakeningType] = useState(0)
|
||||
const [awakeningLevel, setAwakeningLevel] = useState(0)
|
||||
|
||||
// Character properties: Transcendence
|
||||
const [transcendenceStep, setTranscendenceStep] = useState(0)
|
||||
|
||||
// Hooks
|
||||
useEffect(() => {}, [])
|
||||
|
||||
// Methods: UI state management
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
function openSelect(
|
||||
name: 'ring1' | 'ring2' | 'ring3' | 'ring4' | 'earring' | 'awakening'
|
||||
) {
|
||||
setRing1Open(name === 'ring1' ? !ring1Open : false)
|
||||
setRing2Open(name === 'ring2' ? !ring2Open : false)
|
||||
setRing3Open(name === 'ring3' ? !ring3Open : false)
|
||||
setRing4Open(name === 'ring4' ? !ring4Open : false)
|
||||
setEarringOpen(name === 'earring' ? !earringOpen : false)
|
||||
setAwakeningOpen(name === 'awakening' ? !awakeningOpen : false)
|
||||
}
|
||||
|
||||
const anySelectOpen =
|
||||
ring1Open ||
|
||||
ring2Open ||
|
||||
ring3Open ||
|
||||
ring4Open ||
|
||||
earringOpen ||
|
||||
awakeningOpen
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
if (anySelectOpen) {
|
||||
return event.preventDefault()
|
||||
} else {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
function receiveRingOpen(index: 1 | 2 | 3 | 4, isOpen: boolean) {
|
||||
if (index === 1) setRing1Open(isOpen)
|
||||
if (index === 2) setRing2Open(isOpen)
|
||||
if (index === 3) setRing3Open(isOpen)
|
||||
if (index === 4) setRing4Open(isOpen)
|
||||
}
|
||||
|
||||
function receiveEarringOpen(isOpen: boolean) {
|
||||
setEarringOpen(isOpen)
|
||||
}
|
||||
|
||||
function receiveAwakeningOpen(isOpen: boolean) {
|
||||
setAwakeningOpen(isOpen)
|
||||
}
|
||||
|
||||
// Methods: Receive data from components
|
||||
function receiveRingValues(
|
||||
ringModifier1: number,
|
||||
ringModifier2: number,
|
||||
ringModifier3: number,
|
||||
ringModifier4: number,
|
||||
ringStrength1: number,
|
||||
ringStrength2: number,
|
||||
ringStrength3: number,
|
||||
ringStrength4: number
|
||||
) {
|
||||
setRingModifier1(ringModifier1)
|
||||
setRingModifier2(ringModifier2)
|
||||
setRingModifier3(ringModifier3)
|
||||
setRingModifier4(ringModifier4)
|
||||
|
||||
setRingStrength1(ringStrength1)
|
||||
setRingStrength2(ringStrength2)
|
||||
setRingStrength3(ringStrength3)
|
||||
setRingStrength4(ringStrength4)
|
||||
}
|
||||
|
||||
function receiveEarringValues(
|
||||
earringModifier: number,
|
||||
earringStrength: number
|
||||
) {
|
||||
setEarringModifier(earringModifier)
|
||||
setEarringStrength(earringStrength)
|
||||
}
|
||||
|
||||
function receiveAwakeningValues(type: number, level: number) {
|
||||
setAwakeningType(type)
|
||||
setAwakeningLevel(level)
|
||||
}
|
||||
|
||||
function receiveValidity(isValid: boolean) {
|
||||
setFormValid(isValid)
|
||||
}
|
||||
|
||||
// Methods: Data syncing
|
||||
|
||||
// Prepare the GridWeaponObject to send to the server
|
||||
function prepareObject() {
|
||||
let object: GridCharacterObject = {
|
||||
character: {
|
||||
ring_modifier1: ringModifier1,
|
||||
ring_modifier2: ringModifier2,
|
||||
ring_modifier3: ringModifier3,
|
||||
ring_modifier4: ringModifier4,
|
||||
ring_strength1: ringStrength1,
|
||||
ring_strength2: ringStrength2,
|
||||
ring_strength3: ringStrength3,
|
||||
ring_strength4: ringStrength4,
|
||||
awakening_type: awakeningType,
|
||||
awakening_level: awakeningLevel,
|
||||
transcendence_step: transcendenceStep,
|
||||
},
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// Send the GridWeaponObject to the server
|
||||
async function updateCharacter() {
|
||||
const updateObject = prepareObject()
|
||||
return await api.endpoints.grid_characters
|
||||
.update(gridCharacter.id, updateObject)
|
||||
.then((response) => processResult(response))
|
||||
.catch((error) => processError(error))
|
||||
}
|
||||
|
||||
// Save the server's response to state
|
||||
function processResult(response: AxiosResponse) {
|
||||
const gridCharacter: GridCharacter = response.data
|
||||
appState.grid.characters[gridCharacter.position] = gridCharacter
|
||||
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function processError(error: any) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
const ringSelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>{t('modals.characters.subtitles.ring')}</h3>
|
||||
<RingSelect
|
||||
gridCharacter={gridCharacter}
|
||||
onOpenChange={receiveRingOpen}
|
||||
sendValidity={receiveValidity}
|
||||
sendValues={receiveRingValues}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const earringSelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>{t('modals.characters.subtitles.earring')}</h3>
|
||||
<EarringSelect
|
||||
gridCharacter={gridCharacter}
|
||||
onOpenChange={receiveEarringOpen}
|
||||
sendValidity={receiveValidity}
|
||||
sendValues={receiveEarringValues}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const awakeningSelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>{t('modals.weapon.subtitles.awakening')}</h3>
|
||||
<AwakeningSelect
|
||||
object="character"
|
||||
awakeningType={gridCharacter.awakening?.type}
|
||||
awakeningLevel={gridCharacter.awakening?.level}
|
||||
onOpenChange={receiveAwakeningOpen}
|
||||
sendValidity={receiveValidity}
|
||||
sendValues={receiveAwakeningValues}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO: Refactor into Dialog component
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Character Dialog"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTop">
|
||||
<DialogTitle className="SubTitle">
|
||||
{t('modals.character.title')}
|
||||
</DialogTitle>
|
||||
<DialogTitle className="DialogTitle">
|
||||
{gridCharacter.object.name[locale]}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogClose className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
</span>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div className="mods">
|
||||
{ringSelect()}
|
||||
{earringSelect()}
|
||||
{awakeningSelect()}
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={updateCharacter}
|
||||
disabled={!formValid}
|
||||
text={t('modals.character.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default CharacterModal
|
||||
Loading…
Reference in a new issue