Implement AwakeningSelect

* Has modes for weapons and characters
* Shows input when awakening is selected
* Saves type and level to server
* Redisplays type but level is broken
This commit is contained in:
Justin Edmund 2022-12-23 18:40:50 -08:00
parent cf58e64caf
commit c74ff41479
6 changed files with 253 additions and 3 deletions

View file

@ -0,0 +1,31 @@
.AwakeningSelect .AwakeningSet {
.errors {
color: $error;
display: none;
padding: $unit 0;
&.visible {
display: block;
}
}
.fields {
display: flex;
flex-direction: row;
gap: $unit;
width: 100%;
.SelectTrigger {
flex-grow: 1;
}
.Label {
flex-grow: 0;
.Input {
min-width: $unit * 12;
width: inherit;
}
}
}
}

View file

@ -0,0 +1,183 @@
import React, { ForwardedRef, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import Input from '~components/Input'
import Select from '~components/Select'
import SelectItem from '~components/SelectItem'
import classNames from 'classnames'
import { weaponAwakening, characterAwakening } from '~utils/awakening'
import type { Awakening } from '~utils/awakening'
import './index.scss'
interface Props {
object: 'character' | 'weapon'
awakeningType?: number
awakeningLevel?: number
sendValues: (type: number, level: number) => void
}
const AwakeningSelect = (props: Props) => {
const router = useRouter()
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const { t } = useTranslation('common')
const [open, setOpen] = useState(false)
// Refs
const awakeningLevelInput = React.createRef<HTMLInputElement>()
// States
const [awakeningType, setAwakeningType] = useState(-1)
const [awakeningLevel, setAwakeningLevel] = useState(1)
const [maxValue, setMaxValue] = useState(1)
const [error, setError] = useState('')
// Classes
const inputClasses = classNames({
Bound: true,
Hidden: awakeningType === -1,
})
const errorClasses = classNames({
errors: true,
visible: error !== '',
})
// Set max value based on object type
useEffect(() => {
if (props.object === 'character') setMaxValue(9)
else if (props.object === 'weapon') setMaxValue(15)
}, [props.object])
// Set default awakening and level based on object type
useEffect(() => {
let defaultAwakening = 0
if (props.object === 'weapon') defaultAwakening = -1
setAwakeningType(
props.awakeningType != undefined ? props.awakeningType : defaultAwakening
)
setAwakeningLevel(props.awakeningLevel ? props.awakeningLevel : 1)
}, [props.object, props.awakeningType, props.awakeningLevel])
useEffect(() => {
props.sendValues(awakeningType, awakeningLevel)
}, [props.sendValues, awakeningType, awakeningLevel])
// Classes
function changeOpen() {
setOpen(!open)
}
function generateOptions(object: 'character' | 'weapon') {
let options: Awakening[] = []
if (object === 'character') options = characterAwakening
else if (object === 'weapon') options = weaponAwakening
else return
let optionElements: React.ReactNode[] = options.map((awakening, i) => {
return (
<SelectItem key={i} value={awakening.id}>
{awakening.name[locale]}
</SelectItem>
)
})
if (object === 'weapon') {
optionElements?.unshift(
<SelectItem key={-1} value={-1}>
{t('ax.no_skill')}
</SelectItem>
)
}
return optionElements
}
function handleSelectChange(rawValue: string) {
const value = parseInt(rawValue)
setAwakeningType(value)
}
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = parseFloat(event.target.value)
if (handleLevelError(value)) setAwakeningLevel(value)
}
function handleLevelError(value: number) {
let error = ''
if (value < 1) {
error = t('awakening.errors.value_too_low', {
minValue: 1,
})
} else if (value > maxValue) {
error = t('awakening.errors.value_too_high', {
maxValue: maxValue,
})
} else if (!value || value <= 0) {
error = t('awakening.errors.value_empty')
} else {
error = ''
}
setError(error)
return error.length === 0
}
const rangeString = (object: 'character' | 'weapon') => {
let minValue = 1
let maxValue = 1
if (object === 'weapon') {
minValue = 1
maxValue = 15
} else if (object === 'character') {
minValue = 1
maxValue = 9
} else return
return `${minValue}~${maxValue}`
}
return (
<div className="AwakeningSelect">
<div className="AwakeningSet">
<div className="fields">
<Select
key="awakening_type"
value={`${awakeningType}`}
open={open}
onValueChange={handleSelectChange}
onClick={() => changeOpen()}
triggerClass="modal"
>
{generateOptions(props.object)}
</Select>
<Input
value={awakeningLevel}
className={inputClasses}
type="number"
placeholder={rangeString(props.object)}
min={1}
max={maxValue}
step="1"
onChange={handleInputChange}
ref={awakeningLevelInput}
/>
</div>
<p className={errorClasses}>{error}</p>
</div>
</div>
)
}
export default AwakeningSelect

View file

@ -5,7 +5,7 @@
border-radius: 6px;
box-sizing: border-box;
display: block;
padding: ($unit * 1.5) $unit-2x;
padding: $unit-2x;
width: 100%;
&.Bound {
@ -15,6 +15,10 @@
background-color: var(--input-bound-bg-hover);
}
}
&.Hidden {
display: none;
}
}
.InputError {

View file

@ -33,7 +33,6 @@ const Select = (props: Props) => {
if (props.onValueChange) props.onValueChange(newValue)
}
console.log(value)
return (
<RadixSelect.Root
value={value !== '' ? value : undefined}

View file

@ -16,6 +16,7 @@ import { appState } from '~utils/appState'
import CrossIcon from '~public/icons/Cross.svg'
import './index.scss'
import AwakeningSelect from '~components/AwakeningSelect'
interface GridWeaponObject {
weapon: {
@ -27,6 +28,8 @@ interface GridWeaponObject {
ax_modifier2?: number
ax_strength1?: number
ax_strength2?: number
awakening_type?: number
awakening_level?: Number
}
}
@ -56,6 +59,9 @@ const WeaponModal = (props: Props) => {
const [element, setElement] = useState(-1)
const [awakeningType, setAwakeningType] = useState(-1)
const [awakeningLevel, setAwakeningLevel] = useState(1)
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
@ -99,6 +105,11 @@ const WeaponModal = (props: Props) => {
setFormValid(isValid)
}
function receiveAwakeningValues(type: number, level: number) {
setAwakeningType(type)
setAwakeningLevel(level)
}
function receiveElementValue(element: string) {
setElement(parseInt(element))
}
@ -125,6 +136,11 @@ const WeaponModal = (props: Props) => {
object.weapon.ax_strength2 = secondaryAxValue
}
if (props.gridWeapon.object.awakening) {
object.weapon.awakening_type = awakeningType
object.weapon.awakening_level = awakeningLevel
}
return object
}
@ -137,7 +153,8 @@ const WeaponModal = (props: Props) => {
}
function processResult(response: AxiosResponse) {
const gridWeapon: GridWeapon = response.data.grid_weapon
console.log(response)
const gridWeapon: GridWeapon = response.data
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
@ -221,6 +238,20 @@ const WeaponModal = (props: Props) => {
)
}
const awakeningSelect = () => {
return (
<section>
<h3>{t('modals.weapon.subtitles.awakening')}</h3>
<AwakeningSelect
object="weapon"
awakeningType={props.gridWeapon.awakening?.type}
awakeningLevel={props.gridWeapon.awakening?.level}
sendValues={receiveAwakeningValues}
/>
</section>
)
}
function openChange(open: boolean) {
setFormValid(false)
setOpen(open)
@ -256,6 +287,7 @@ const WeaponModal = (props: Props) => {
? keySelect()
: ''}
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
{props.gridWeapon.awakening ? awakeningSelect() : ''}
<Button
contained={true}
onClick={updateWeapon}

View file

@ -80,6 +80,7 @@ const WeaponUnit = (props: Props) => {
return (
weapon.ax > 0 ||
weapon.awakening ||
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
)
}