'use client' import React, { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { useTranslations } from 'next-intl' import { AxiosResponse } from 'axios' import classNames from 'classnames' import clonedeep from 'lodash.clonedeep' import { getCookie } from 'cookies-next' 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 updateTranscendence: (id: string, position: number, stage: number) => void } const WeaponUnit = ({ gridWeapon, unitType, position, editable, removeWeapon: sendWeaponToRemove, updateObject, updateUncap, updateTranscendence, }: Props) => { // Translations and locale const t = useTranslations('common') const router = useRouter() const locale = getCookie('NEXT_LOCALE') && ['en', 'ja'].includes(getCookie('NEXT_LOCALE') as string) ? (getCookie('NEXT_LOCALE') as string) : '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 passTranscendenceData(stage: number) { if (gridWeapon) updateTranscendence(gridWeapon.id, position, stage) } 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! let suffix = '' if (weapon.uncap.transcendence && gridWeapon.uncap_level == 6) { if ( gridWeapon.transcendence_step >= 1 && gridWeapon.transcendence_step < 5 ) { suffix = '_02' } else if (gridWeapon.transcendence_step === 5) { suffix = '_03' } } 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}${suffix}.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}${suffix}.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}` let elementalTelumas = [15008, 16001, 16002] let granblueId = parseInt(weaponKey.granblue_id) if (elementalTelumas.includes(granblueId)) { 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 ( <>