Add perpetuity control to CharacterUnit

This involved moving the code to update a GridCharacter to the CharacterUnit from CharacterModal
This commit is contained in:
Justin Edmund 2023-01-21 20:20:25 -08:00
parent 942142f19a
commit 4f7d18904f
4 changed files with 145 additions and 69 deletions

View file

@ -41,28 +41,17 @@ import CrossIcon from '~public/icons/Cross.svg'
import './index.scss' import './index.scss'
// Types // Types
import { CharacterOverMastery, ExtendedMastery } from '~types' import {
CharacterOverMastery,
interface GridCharacterObject { ExtendedMastery,
character: { GridCharacterObject,
ring1: ExtendedMastery } from '~types'
ring2: ExtendedMastery
ring3: ExtendedMastery
ring4: ExtendedMastery
earring: ExtendedMastery
awakening: {
type?: number
level?: number
}
transcendence_step: number
perpetuity: boolean
}
}
interface Props { interface Props {
gridCharacter: GridCharacter gridCharacter: GridCharacter
open: boolean open: boolean
onOpenChange: (open: boolean) => void onOpenChange: (open: boolean) => void
updateCharacter: (object: GridCharacterObject) => Promise<any>
} }
const CharacterModal = ({ const CharacterModal = ({
@ -70,6 +59,7 @@ const CharacterModal = ({
children, children,
open: modalOpen, open: modalOpen,
onOpenChange, onOpenChange,
updateCharacter,
}: PropsWithChildren<Props>) => { }: PropsWithChildren<Props>) => {
const router = useRouter() const router = useRouter()
const locale = const locale =
@ -133,42 +123,6 @@ const CharacterModal = ({
setPerpetuity(gridCharacter.perpetuity) setPerpetuity(gridCharacter.perpetuity)
}, [gridCharacter]) }, [gridCharacter])
// Methods: UI state management
function handleOpenChange(open: boolean) {
setOpen(open)
onOpenChange(open)
}
// Methods: Receive data from components
function receiveRingValues(overMastery: CharacterOverMastery) {
setRings(overMastery)
}
function receiveEarringValues(
earringModifier: number,
earringStrength: number
) {
setEarring({
modifier: earringModifier,
strength: earringStrength,
})
}
function handleCheckedChange(checked: boolean) {
setPerpetuity(checked)
}
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 // Prepare the GridWeaponObject to send to the server
function prepareObject() { function prepareObject() {
let object: GridCharacterObject = { let object: GridCharacterObject = {
@ -205,27 +159,45 @@ const CharacterModal = ({
return object return object
} }
// Send the GridWeaponObject to the server // Methods: UI state management
async function updateCharacter() { function handleOpenChange(open: boolean) {
const updateObject = prepareObject() setOpen(open)
onOpenChange(open)
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 // Methods: Receive data from components
function processResult(response: AxiosResponse) { function receiveRingValues(overMastery: CharacterOverMastery) {
const gridCharacter: GridCharacter = response.data setRings(overMastery)
appState.grid.characters[gridCharacter.position] = gridCharacter }
function receiveEarringValues(
earringModifier: number,
earringStrength: number
) {
setEarring({
modifier: earringModifier,
strength: earringStrength,
})
}
function handleCheckedChange(checked: boolean) {
setPerpetuity(checked)
}
async function handleUpdateCharacter() {
await updateCharacter(prepareObject())
setOpen(false) setOpen(false)
if (onOpenChange) onOpenChange(false) if (onOpenChange) onOpenChange(false)
} }
function processError(error: any) { function receiveAwakeningValues(type: number, level: number) {
console.error(error) setAwakeningType(type)
setAwakeningLevel(level)
}
function receiveValidity(isValid: boolean) {
setFormValid(isValid)
} }
const ringSelect = () => { const ringSelect = () => {
@ -322,7 +294,7 @@ const CharacterModal = ({
<div className="DialogFooter" ref={footerRef}> <div className="DialogFooter" ref={footerRef}>
<Button <Button
contained={true} contained={true}
onClick={updateCharacter} onClick={handleUpdateCharacter}
disabled={!formValid} disabled={!formValid}
text={t('modals.characters.buttons.confirm')} text={t('modals.characters.buttons.confirm')}
/> />

View file

@ -97,4 +97,34 @@
font-size: $font-tiny; font-size: $font-tiny;
} }
} }
&:hover .Perpetuity.Empty {
opacity: 1;
}
.Perpetuity {
position: absolute;
background-image: url('/icons/perpetuity/filled.svg');
background-size: $unit-4x $unit-4x;
z-index: 20;
top: $unit * -1;
right: $unit-3x;
width: $unit-4x;
height: $unit-4x;
transition: $duration-zoom opacity ease-in-out;
&:hover {
background-image: url('/icons/perpetuity/empty.svg');
cursor: pointer;
}
&.Empty {
background-image: url('/icons/perpetuity/empty.svg');
opacity: 0;
&:hover {
background-image: url('/icons/perpetuity/filled.svg');
}
}
}
} }

View file

@ -2,6 +2,7 @@ import React, { MouseEvent, useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { Trans, useTranslation } from 'next-i18next' import { Trans, useTranslation } from 'next-i18next'
import { AxiosResponse } from 'axios'
import classNames from 'classnames' import classNames from 'classnames'
import Alert from '~components/Alert' import Alert from '~components/Alert'
@ -17,12 +18,18 @@ import ContextMenuItem from '~components/ContextMenuItem'
import SearchModal from '~components/SearchModal' import SearchModal from '~components/SearchModal'
import UncapIndicator from '~components/UncapIndicator' import UncapIndicator from '~components/UncapIndicator'
import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
import SettingsIcon from '~public/icons/Settings.svg' import SettingsIcon from '~public/icons/Settings.svg'
import type { SearchableObject } from '~types' // Types
import type {
GridCharacterObject,
PerpetuityObject,
SearchableObject,
} from '~types'
import './index.scss' import './index.scss'
@ -99,6 +106,16 @@ const CharacterUnit = ({
setContextMenuOpen(!contextMenuOpen) setContextMenuOpen(!contextMenuOpen)
} }
function handlePerpetuityClick() {
if (gridCharacter) {
let object: PerpetuityObject = {
character: { perpetuity: !gridCharacter.perpetuity },
}
updateCharacter(object)
}
}
// Methods: Handle open change // Methods: Handle open change
function handleCharacterModalOpenChange(open: boolean) { function handleCharacterModalOpenChange(open: boolean) {
setDetailsModalOpen(open) setDetailsModalOpen(open)
@ -113,6 +130,28 @@ const CharacterUnit = ({
} }
// Methods: Mutate data // Methods: Mutate data
// Send the GridWeaponObject to the server
async function updateCharacter(
object: GridCharacterObject | PerpetuityObject
) {
if (gridCharacter)
return await api.endpoints.grid_characters
.update(gridCharacter.id, object)
.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
}
function processError(error: any) {
console.error(error)
}
function passUncapData(uncap: number) { function passUncapData(uncap: number) {
if (gridCharacter) updateUncap(gridCharacter.id, position, uncap) if (gridCharacter) updateUncap(gridCharacter.id, position, uncap)
} }
@ -160,6 +199,7 @@ const CharacterUnit = ({
gridCharacter={gridCharacter} gridCharacter={gridCharacter}
open={detailsModalOpen} open={detailsModalOpen}
onOpenChange={handleCharacterModalOpenChange} onOpenChange={handleCharacterModalOpenChange}
updateCharacter={updateCharacter}
/> />
) )
} }
@ -228,6 +268,17 @@ const CharacterUnit = ({
} }
// Methods: Core element rendering // Methods: Core element rendering
const perpetuity = () => {
if (gridCharacter) {
const classes = classNames({
Perpetuity: true,
Empty: !gridCharacter.perpetuity,
})
return <i className={classes} onClick={handlePerpetuityClick} />
}
}
const image = ( const image = (
<div className="CharacterImage" onClick={openSearchModal}> <div className="CharacterImage" onClick={openSearchModal}>
<img <img
@ -249,6 +300,7 @@ const CharacterUnit = ({
<> <>
<div className={classes}> <div className={classes}>
{contextMenu()} {contextMenu()}
{perpetuity()}
{image} {image}
{gridCharacter && character ? ( {gridCharacter && character ? (
<UncapIndicator <UncapIndicator

22
types/index.d.ts vendored
View file

@ -48,3 +48,25 @@ export type CharacterOverMastery = {
3: ExtendedMastery 3: ExtendedMastery
4: ExtendedMastery 4: ExtendedMastery
} }
interface GridCharacterObject {
character: {
ring1: ExtendedMastery
ring2: ExtendedMastery
ring3: ExtendedMastery
ring4: ExtendedMastery
earring: ExtendedMastery
awakening: {
type?: number
level?: number
}
transcendence_step: number
perpetuity: boolean
}
}
interface PerpetuityObject {
character: {
perpetuity: boolean
}
}