import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { Trans, useTranslation } from 'next-i18next' import { AxiosResponse } from 'axios' import classNames from 'classnames' import clonedeep from 'lodash.clonedeep' import api from '~utils/api' import { appState } from '~utils/appState' import Alert from '~components/common/Alert' import SearchModal from '~components/search/SearchModal' import WeaponModal from '~components/weapon/WeaponModal' import { ContextMenu, ContextMenuTrigger, ContextMenuContent, } from '~components/common/ContextMenu' import ContextMenuItem from '~components/common/ContextMenuItem' import WeaponHovercard from '~components/weapon/WeaponHovercard' import UncapIndicator from '~components/uncap/UncapIndicator' import Button from '~components/common/Button' import type { GridWeaponObject, SearchableObject } from '~types' import ax from '~data/ax' import PlusIcon from '~public/icons/Add.svg' import SettingsIcon from '~public/icons/Settings.svg' import styles from './index.module.scss' interface Props { gridWeapon: GridWeapon | undefined unitType: 0 | 1 position: number editable: boolean removeWeapon: (id: string) => void updateObject: (object: SearchableObject, position: number) => void updateUncap: (id: string, position: number, uncap: number) => void } const WeaponUnit = ({ gridWeapon, unitType, position, editable, removeWeapon: sendWeaponToRemove, updateObject, updateUncap, }: Props) => { // Translations and locale const { t } = useTranslation('common') const router = useRouter() const locale = router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' // State: UI const [detailsModalOpen, setDetailsModalOpen] = useState(false) const [searchModalOpen, setSearchModalOpen] = useState(false) const [contextMenuOpen, setContextMenuOpen] = useState(false) const [alertOpen, setAlertOpen] = useState(false) // State: Other const [imageUrl, setImageUrl] = useState('') // Classes const classes = classNames({ unit: true, [styles.unit]: true, [styles.extra]: position >= 9, [styles.mainhand]: unitType == 0, [styles.weapon]: unitType == 1, [styles.editable]: editable, [styles.filled]: gridWeapon !== undefined, [styles.empty]: gridWeapon == undefined, }) // Other const weapon = gridWeapon?.object // Hooks useEffect(() => { generateImageUrl() }) // Methods: Convenience function canBeModified(gridWeapon: GridWeapon) { const weapon = gridWeapon.object return ( weapon.ax || weapon.awakenings || (weapon.series && [2, 3, 17, 22, 24, 34].includes(weapon.series)) ) } // Methods: Open layer function openWeaponModal(event: Event) { setDetailsModalOpen(true) } function openSearchModal() { if (editable) setSearchModalOpen(true) } function openRemoveWeaponAlert() { setAlertOpen(true) } // Methods: Handle button clicked function handleButtonClicked() { setContextMenuOpen(!contextMenuOpen) } // Methods: Handle open change function handleContextMenuOpenChange(open: boolean) { if (!open) setContextMenuOpen(false) } function handleWeaponModalOpenChange(open: boolean) { setDetailsModalOpen(open) } function handleSearchModalOpenChange(open: boolean) { setSearchModalOpen(open) } // Methods: Mutate data function passUncapData(index: number) { if (gridWeapon) updateUncap(gridWeapon.id, position, index) } function removeWeapon() { if (gridWeapon) sendWeaponToRemove(gridWeapon.id) setAlertOpen(false) } // Methods: Data fetching and manipulation async function updateWeapon(object: GridWeaponObject) { if (gridWeapon) { return await api.endpoints.grid_weapons .update(gridWeapon.id, object) .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 appState.party.element = gridWeapon.object.element } else if (!gridWeapon.mainhand && gridWeapon.position !== null) { let weapon = clonedeep(gridWeapon) if (weapon.object.element === 0 && weapon.element < 1) weapon.element = gridWeapon.object.element appState.grid.weapons.allWeapons[gridWeapon.position] = weapon } } function processError(error: any) { console.error(error) } // Methods: Data fetching and manipulation function getCanonicalAxSkill(index: number) { if ( gridWeapon && gridWeapon.object.ax && gridWeapon.object.ax_type > 0 && gridWeapon.ax ) { const axOptions = ax[gridWeapon.object.ax_type - 1] const weaponAxSkill: SimpleAxSkill = gridWeapon.ax[0] let axSkill = axOptions.find((ax) => ax.id === weaponAxSkill.modifier) if (index !== 0 && axSkill && axSkill.secondary) { const weaponSubAxSkill: SimpleAxSkill = gridWeapon.ax[1] axSkill = axSkill.secondary.find( (ax) => ax.id === weaponSubAxSkill.modifier ) } return axSkill } else return } // Methods: Image string generation function generateImageUrl() { let imgSrc = '' if (gridWeapon) { const weapon = gridWeapon.object! if (unitType == 0) { if (gridWeapon.object.element == 0 && gridWeapon.element) imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${gridWeapon.element}.jpg` else imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg` } else { if (gridWeapon.object.element == 0 && gridWeapon.element) imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg` else imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg` } } setImageUrl(imgSrc) } function placeholderImageUrl() { return unitType == 0 ? '/images/placeholders/placeholder-weapon-main.png' : '/images/placeholders/placeholder-weapon-grid.png' } // Methods: Image element rendering function awakeningImage() { if ( gridWeapon && gridWeapon.object.awakenings && gridWeapon.awakening && gridWeapon.awakening.type ) { const awakening = gridWeapon.awakening return ( {`${awakening.type.name[locale]} ) } } function telumaImage(index: number) { const baseUrl = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-keys/` // If there is a grid weapon, it is a Draconic Weapon and it has keys if ( gridWeapon && (gridWeapon.object.series === 3 || gridWeapon.object.series == 34) && gridWeapon.weapon_keys ) { const weaponKey = gridWeapon.weapon_keys[index] const altText = weaponKey.name[locale] let filename = `${weaponKey.slug}` if ( index === 1 || (index === 2 && parseInt(weaponKey.granblue_id) === 15008) ) { filename += `-${gridWeapon.object.element}` } return ( {altText} ) } } function telumaImages() { let images: JSX.Element[] = [] if ( gridWeapon && (gridWeapon.object.series === 3 || gridWeapon.object.series === 34) && gridWeapon.weapon_keys && gridWeapon.weapon_keys.length > 0 ) { for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { const image = telumaImage(i) if (image) images.push(image) } } return images } function ultimaImage(index: number) { const baseUrl = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-keys/` // If there is a grid weapon, it is a Dark Opus Weapon and it has keys if ( gridWeapon && gridWeapon.object.series === 17 && gridWeapon.weapon_keys ) { const weaponKey = gridWeapon.weapon_keys[index] const altText = weaponKey.name[locale] let filename = weaponKey.slug if (weaponKey.slot === 0) { filename += `-${gridWeapon.object.proficiency}` } return ( {altText} ) } } function ultimaImages() { let images: JSX.Element[] = [] if ( gridWeapon && gridWeapon.object.series === 17 && gridWeapon.weapon_keys && gridWeapon.weapon_keys.length > 0 ) { for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { const image = ultimaImage(i) if (image) images.push(image) } } return images } function opusImage(index: number) { const baseUrl = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-keys/` // If there is a grid weapon, it is a Dark Opus Weapon and it has keys if ( gridWeapon && gridWeapon.object.series === 2 && gridWeapon.weapon_keys ) { const weaponKey = gridWeapon.weapon_keys[index] const altText = weaponKey.name[locale] let filename = weaponKey.slug if (weaponKey.slot === 1) { const element = gridWeapon.object.element const mod = gridWeapon.object.name.en.includes('Repudiation') ? 'primal' : 'magna' const suffixes = [ 'pendulum-strength', 'pendulum-zeal', 'pendulum-strife', 'chain-temperament', 'chain-restoration', 'chain-glorification', ] if (suffixes.includes(weaponKey.slug)) { filename += `-${mod}-${element}` } } return ( {altText} ) } } function opusImages() { let images: JSX.Element[] = [] if ( gridWeapon && gridWeapon.object.series === 2 && gridWeapon.weapon_keys && gridWeapon.weapon_keys.length > 0 ) { for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { const image = opusImage(i) if (image) images.push(image) } } return images } function axImage(index: number) { const axSkill = getCanonicalAxSkill(index) if ( gridWeapon && gridWeapon.object.ax && gridWeapon.object.ax_type > 0 && gridWeapon.ax && axSkill ) { const altText = `${axSkill.name[locale]} Lv${gridWeapon.ax[index].strength}` return ( {altText} ) } } function axImages() { let images: JSX.Element[] = [] if ( gridWeapon && gridWeapon.object.ax && gridWeapon.ax && gridWeapon.ax.length > 0 ) { const numSkills = gridWeapon.ax[1].modifier ? 2 : 1 for (let i = 0; i < numSkills; i++) { const image = axImage(i) if (image) images.push(image) } } return images } // Methods: Layer element rendering const weaponModal = () => { if (gridWeapon) { return ( ) } } const contextMenu = () => { if (editable && gridWeapon && gridWeapon.id) { return ( <>