Merge pull request #50 from jedmund/grid-mechanics
Implement grid mechanics
This commit is contained in:
commit
98710539e6
14 changed files with 544 additions and 108 deletions
|
|
@ -3,7 +3,6 @@ import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
|||
|
||||
import './index.scss'
|
||||
import Button from '~components/Button'
|
||||
import { ButtonType } from '~utils/enums'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
.Conflict.Dialog {
|
||||
& > p {
|
||||
line-height: 1.2;
|
||||
max-width: 400px;
|
||||
}
|
||||
img {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
color: $grey-55;
|
||||
font-size: 4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.character {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
text-align: center;
|
||||
width: 12rem;
|
||||
font-weight: $medium;
|
||||
}
|
||||
|
||||
.diagram {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * 2;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
|
||||
.Button {
|
||||
font-size: $font-regular;
|
||||
padding: ($unit * 1.5) ($unit * 2);
|
||||
width: 100%;
|
||||
|
||||
&.btn-disabled {
|
||||
background: $grey-90;
|
||||
color: $grey-70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(.btn-disabled) {
|
||||
background: $grey-90;
|
||||
color: $grey-50;
|
||||
|
||||
&:hover {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { setCookie } from 'cookies-next'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AxiosResponse } from 'axios'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Trans, useTranslation } from 'next-i18next'
|
||||
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
|
||||
import api from '~utils/api'
|
||||
import { appState } from '~utils/appState'
|
||||
import { accountState } from '~utils/accountState'
|
||||
|
||||
import Button from '~components/Button'
|
||||
|
||||
|
|
@ -24,7 +20,11 @@ interface Props {
|
|||
}
|
||||
|
||||
const CharacterConflictModal = (props: Props) => {
|
||||
// Localization
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale =
|
||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
|
|
@ -60,11 +60,11 @@ const CharacterConflictModal = (props: Props) => {
|
|||
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
props.resetConflict()
|
||||
}
|
||||
|
||||
function close() {
|
||||
setOpen(false)
|
||||
props.resetConflict()
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -75,33 +75,37 @@ const CharacterConflictModal = (props: Props) => {
|
|||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<p>
|
||||
Only one version of a character can be included in each party. Do
|
||||
you want to change your party members?
|
||||
<Trans i18nKey="modals.conflict.character"></Trans>
|
||||
</p>
|
||||
<div className="diagram">
|
||||
<div className="CharacterDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingCharacters?.map((character, i) => (
|
||||
<li className="character" key={`conflict-${i}`}>
|
||||
<img
|
||||
alt={character.object.name.en}
|
||||
alt={character.object.name[locale]}
|
||||
src={imageUrl(character.object, character.uncap_level)}
|
||||
/>
|
||||
<span>{character.object.name.en}</span>
|
||||
<span>{character.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="character">
|
||||
<img
|
||||
alt={props.incomingCharacter?.name.en}
|
||||
src={imageUrl(props.incomingCharacter)}
|
||||
/>
|
||||
{props.incomingCharacter?.name.en}
|
||||
<div className="wrapper">
|
||||
<div className="character">
|
||||
<img
|
||||
alt={props.incomingCharacter?.name[locale]}
|
||||
src={imageUrl(props.incomingCharacter)}
|
||||
/>
|
||||
<span>{props.incomingCharacter?.name[locale]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<Button onClick={close} text="Nevermind" />
|
||||
<Button onClick={props.resolveConflict} text="Confirm" />
|
||||
<Button onClick={close} text={t('buttons.cancel')} />
|
||||
<Button
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</footer>
|
||||
</Dialog.Content>
|
||||
<Dialog.Overlay className="Overlay" />
|
||||
|
|
|
|||
|
|
@ -145,7 +145,8 @@ const CharacterGrid = (props: Props) => {
|
|||
async function resolveConflict() {
|
||||
if (incoming && conflicts.length > 0) {
|
||||
await api
|
||||
.resolveCharacterConflict({
|
||||
.resolveConflict({
|
||||
object: 'characters',
|
||||
incoming: incoming.id,
|
||||
conflicting: conflicts.map((c) => c.id),
|
||||
position: position,
|
||||
|
|
|
|||
|
|
@ -92,4 +92,101 @@
|
|||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.Conflict.Dialog {
|
||||
$weapon-diameter: 14rem;
|
||||
|
||||
& > p {
|
||||
line-height: 1.2;
|
||||
max-width: 400px;
|
||||
|
||||
strong {
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&:lang(ja) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.weapon,
|
||||
.character {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
text-align: center;
|
||||
width: $weapon-diameter;
|
||||
font-weight: $medium;
|
||||
|
||||
img {
|
||||
border-radius: 1rem;
|
||||
width: $weapon-diameter;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.Diagram {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: flex-start;
|
||||
|
||||
&.CharacterDiagram {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ul {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * 2;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
align-items: center;
|
||||
color: $grey-55;
|
||||
display: flex;
|
||||
font-size: 4rem;
|
||||
text-align: center;
|
||||
height: $weapon-diameter;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
|
||||
.Button {
|
||||
font-size: $font-regular;
|
||||
padding: ($unit * 1.5) ($unit * 2);
|
||||
width: 100%;
|
||||
|
||||
&.btn-disabled {
|
||||
background: $grey-90;
|
||||
color: $grey-70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(.btn-disabled) {
|
||||
background: $grey-90;
|
||||
color: $grey-50;
|
||||
|
||||
&:hover {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,9 +151,22 @@ const SearchModal = (props: Props) => {
|
|||
openChange()
|
||||
}
|
||||
|
||||
const extraPositions = () => {
|
||||
if (props.object === 'weapons') return [9, 10, 11]
|
||||
else if (props.object === 'summons') return [4, 5]
|
||||
else return []
|
||||
}
|
||||
|
||||
function receiveFilters(filters: { [key: string]: any }) {
|
||||
setCurrentPage(1)
|
||||
setResults([])
|
||||
|
||||
// Only show extra or subaura objects if invoked from those positions
|
||||
if (extraPositions().includes(props.fromPosition)) {
|
||||
if (props.object === 'weapons') filters.extra = true
|
||||
else if (props.object === 'summons') filters.subaura = true
|
||||
}
|
||||
|
||||
setFilters(filters)
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +188,12 @@ const SearchModal = (props: Props) => {
|
|||
: []
|
||||
|
||||
if (open) {
|
||||
if (firstLoad && cookieObj && cookieObj.length > 0) {
|
||||
if (
|
||||
firstLoad &&
|
||||
cookieObj &&
|
||||
cookieObj.length > 0 &&
|
||||
!extraPositions().includes(props.fromPosition)
|
||||
) {
|
||||
setResults(cookieObj)
|
||||
setRecordCount(cookieObj.length)
|
||||
setFirstLoad(false)
|
||||
|
|
|
|||
0
components/WeaponConflictModal/index.scss
Normal file
0
components/WeaponConflictModal/index.scss
Normal file
108
components/WeaponConflictModal/index.tsx
Normal file
108
components/WeaponConflictModal/index.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import Button from '~components/Button'
|
||||
|
||||
import mapWeaponSeries from '~utils/mapWeaponSeries'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
incomingWeapon: Weapon
|
||||
conflictingWeapons: GridWeapon[]
|
||||
desiredPosition: number
|
||||
resolveConflict: () => void
|
||||
resetConflict: () => void
|
||||
}
|
||||
|
||||
const WeaponConflictModal = (props: Props) => {
|
||||
// Localization
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale =
|
||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(props.open)
|
||||
}, [setOpen, props.open])
|
||||
|
||||
function imageUrl(weapon?: Weapon) {
|
||||
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${weapon?.granblue_id}.jpg`
|
||||
}
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
props.resetConflict()
|
||||
}
|
||||
|
||||
function close() {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const infoString = () => {
|
||||
const series = props.incomingWeapon.series
|
||||
const seriesSlug = t(`series.${mapWeaponSeries(series)}`)
|
||||
|
||||
return [2, 3].includes(series) ? (
|
||||
<Trans i18nKey="modals.conflict.weapon.opus-draconic"></Trans>
|
||||
) : (
|
||||
<Trans i18nKey="modals.conflict.weapon.generic">
|
||||
Only one weapon from the
|
||||
<strong>{{ series: seriesSlug }} Series</strong> can be included in each
|
||||
party. Do you want to change your weapons?
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content
|
||||
className="Conflict Dialog"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<p>{infoString()}</p>
|
||||
<div className="WeaponDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingWeapons?.map((weapon, i) => (
|
||||
<li className="weapon" key={`conflict-${i}`}>
|
||||
<img
|
||||
alt={weapon.object.name[locale]}
|
||||
src={imageUrl(weapon.object)}
|
||||
/>
|
||||
<span>{weapon.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="wrapper">
|
||||
<div className="weapon">
|
||||
<img
|
||||
alt={props.incomingWeapon?.name[locale]}
|
||||
src={imageUrl(props.incomingWeapon)}
|
||||
/>
|
||||
{props.incomingWeapon?.name[locale]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<Button onClick={close} text={t('buttons.cancel')} />
|
||||
<Button
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</footer>
|
||||
</Dialog.Content>
|
||||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default WeaponConflictModal
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { getCookie } from 'cookies-next'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import { AxiosResponse } from 'axios'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
|
@ -15,6 +16,8 @@ import { appState } from '~utils/appState'
|
|||
import type { SearchableObject } from '~types'
|
||||
|
||||
import './index.scss'
|
||||
import WeaponConflictModal from '~components/WeaponConflictModal'
|
||||
import Alert from '~components/Alert'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
|
|
@ -28,18 +31,25 @@ const WeaponGrid = (props: Props) => {
|
|||
// Constants
|
||||
const numWeapons: number = 9
|
||||
|
||||
// Localization
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Cookies
|
||||
const cookie = getCookie('account')
|
||||
const accountData: AccountCookie = cookie
|
||||
? JSON.parse(cookie as string)
|
||||
: null
|
||||
const headers = accountData
|
||||
? { headers: { Authorization: `Bearer ${accountData.token}` } }
|
||||
: {}
|
||||
|
||||
// Set up state for view management
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
const [slug, setSlug] = useState()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false)
|
||||
|
||||
// Set up state for conflict management
|
||||
const [incoming, setIncoming] = useState<Weapon>()
|
||||
const [conflicts, setConflicts] = useState<GridWeapon[]>([])
|
||||
const [position, setPosition] = useState(0)
|
||||
|
||||
// Create a temporary state to store previous weapon uncap values
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
|
|
@ -90,9 +100,45 @@ const WeaponGrid = (props: Props) => {
|
|||
)
|
||||
})
|
||||
} else {
|
||||
saveWeapon(party.id, weapon, position).then((response) =>
|
||||
storeGridWeapon(response.data)
|
||||
)
|
||||
if (party.editable)
|
||||
saveWeapon(party.id, weapon, position)
|
||||
.then((response) => handleWeaponResponse(response.data))
|
||||
.catch((error) => {
|
||||
const code = error.response.status
|
||||
const data = error.response.data
|
||||
console.log(error.response)
|
||||
|
||||
console.log(data, code)
|
||||
if (code === 422) {
|
||||
if (data.code === 'incompatible_weapon_for_position') {
|
||||
console.log('Here')
|
||||
setShowIncompatibleAlert(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWeaponResponse(data: any) {
|
||||
if (data.hasOwnProperty('conflicts')) {
|
||||
if (data.incoming) setIncoming(data.incoming)
|
||||
if (data.conflicts) setConflicts(data.conflicts)
|
||||
if (data.position) setPosition(data.position)
|
||||
setModalOpen(true)
|
||||
} else {
|
||||
storeGridWeapon(data.grid_weapon)
|
||||
|
||||
// If we replaced an existing weapon, remove it from the grid
|
||||
if (data.hasOwnProperty('meta') && data.meta['replaced'] !== undefined) {
|
||||
const position = data.meta['replaced']
|
||||
|
||||
if (position == -1) {
|
||||
appState.grid.weapons.mainWeapon = undefined
|
||||
appState.party.element = 0
|
||||
} else {
|
||||
appState.grid.weapons.allWeapons[position] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,18 +147,15 @@ const WeaponGrid = (props: Props) => {
|
|||
if (weapon.uncap.ulb) uncapLevel = 5
|
||||
else if (weapon.uncap.flb) uncapLevel = 4
|
||||
|
||||
return await api.endpoints.weapons.create(
|
||||
{
|
||||
weapon: {
|
||||
party_id: partyId,
|
||||
weapon_id: weapon.id,
|
||||
position: position,
|
||||
mainhand: position == -1,
|
||||
uncap_level: uncapLevel,
|
||||
},
|
||||
return await api.endpoints.weapons.create({
|
||||
weapon: {
|
||||
party_id: partyId,
|
||||
weapon_id: weapon.id,
|
||||
position: position,
|
||||
mainhand: position == -1,
|
||||
uncap_level: uncapLevel,
|
||||
},
|
||||
headers
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function storeGridWeapon(gridWeapon: GridWeapon) {
|
||||
|
|
@ -125,6 +168,44 @@ const WeaponGrid = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
async function resolveConflict() {
|
||||
if (incoming && conflicts.length > 0) {
|
||||
await api
|
||||
.resolveConflict({
|
||||
object: 'weapons',
|
||||
incoming: incoming.id,
|
||||
conflicting: conflicts.map((c) => c.id),
|
||||
position: position,
|
||||
})
|
||||
.then((response) => {
|
||||
// Store new character in state
|
||||
storeGridWeapon(response.data)
|
||||
|
||||
// Remove conflicting characters from state
|
||||
conflicts.forEach((c) => {
|
||||
if (appState.grid.weapons.mainWeapon?.object.id === c.id) {
|
||||
appState.grid.weapons.mainWeapon = undefined
|
||||
appState.party.element = 0
|
||||
} else {
|
||||
appState.grid.weapons.allWeapons[c.position] = undefined
|
||||
}
|
||||
})
|
||||
|
||||
// Reset conflict
|
||||
resetConflict()
|
||||
|
||||
// Close modal
|
||||
setModalOpen(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function resetConflict() {
|
||||
setPosition(-1)
|
||||
setConflicts([])
|
||||
setIncoming(undefined)
|
||||
}
|
||||
|
||||
// Methods: Updating uncap level
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
|
|
@ -242,8 +323,39 @@ const WeaponGrid = (props: Props) => {
|
|||
/>
|
||||
)
|
||||
|
||||
const conflictModal = () => {
|
||||
return incoming && conflicts ? (
|
||||
<WeaponConflictModal
|
||||
open={modalOpen}
|
||||
incomingWeapon={incoming}
|
||||
conflictingWeapons={conflicts}
|
||||
desiredPosition={position}
|
||||
resolveConflict={resolveConflict}
|
||||
resetConflict={resetConflict}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
const incompatibleAlert = () => {
|
||||
console.log(t('alert.incompatible_weapon'))
|
||||
return showIncompatibleAlert ? (
|
||||
<Alert
|
||||
open={showIncompatibleAlert}
|
||||
cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)}
|
||||
cancelActionText={t('buttons.confirm')}
|
||||
message={t('alert.incompatible_weapon')}
|
||||
></Alert>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="WeaponGrid">
|
||||
{conflicts ? conflictModal() : ''}
|
||||
{incompatibleAlert()}
|
||||
<div id="MainGrid">
|
||||
{mainhandElement}
|
||||
<ul className="grid_weapons">{weaponGridElement}</ul>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"alert": {
|
||||
"incompatible_weapon": "You've selected a weapon that can't be added to the Additional Weapon slots."
|
||||
},
|
||||
"ax": {
|
||||
"no_skill": "No AX Skill",
|
||||
"errors": {
|
||||
|
|
@ -20,6 +23,7 @@
|
|||
"buttons": {
|
||||
"cancel": "Cancel",
|
||||
"copy": "Copy link",
|
||||
"confirm": "Got it",
|
||||
"delete": "Delete team",
|
||||
"show_info": "Edit info",
|
||||
"hide_info": "Hide info",
|
||||
|
|
@ -86,6 +90,7 @@
|
|||
"olden_primal": "Olden Primal",
|
||||
"beast": "Beast",
|
||||
"omega": "Omega",
|
||||
"regalia": "Regalia",
|
||||
"militis": "Militis",
|
||||
"xeno": "Xeno",
|
||||
"astral": "Astral",
|
||||
|
|
@ -101,7 +106,10 @@
|
|||
"vintage": "Vintage",
|
||||
"class_champion": "Class Champion",
|
||||
"sephira": "Sephira",
|
||||
"new_world": "New World Foundation"
|
||||
"new_world": "New World Foundation",
|
||||
"revenant": "Revenant",
|
||||
"proving": "Proving Grounds",
|
||||
"disaster": "Disaster"
|
||||
},
|
||||
"recency": {
|
||||
"all_time": "All time",
|
||||
|
|
@ -122,6 +130,16 @@
|
|||
"about": {
|
||||
"title": "About"
|
||||
},
|
||||
"conflict": {
|
||||
"character": "Only one <strong>version of a character</strong> can be included in each party. Do you want to change your party members?",
|
||||
"weapon": {
|
||||
"generic": "Only one weapon from the <strong>{{series}} Series</strong> can be included in each party. Do you want to change your weapons?",
|
||||
"opus-draconic": "Only one <strong>Dark Opus</strong> or <strong>Draconic Weapon</strong> can be included in each party. Do you want to change your weapons?"
|
||||
},
|
||||
"buttons": {
|
||||
"confirm": "Confirm"
|
||||
}
|
||||
},
|
||||
"delete_team": {
|
||||
"title": "Delete team",
|
||||
"description": "Are you sure you want to permanently delete this team?",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"alert": {
|
||||
"incompatible_weapon": "Additional Weaponsに装備できない武器を入れました。"
|
||||
},
|
||||
"ax": {
|
||||
"no_skill": "EXスキルなし",
|
||||
"errors": {
|
||||
|
|
@ -18,8 +21,9 @@
|
|||
}
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "キャンセルs",
|
||||
"cancel": "キャンセル",
|
||||
"copy": "リンクをコピー",
|
||||
"confirm": "OK",
|
||||
"delete": "編成を削除",
|
||||
"show_info": "詳細を編集",
|
||||
"save_info": "詳細を保存",
|
||||
|
|
@ -86,6 +90,7 @@
|
|||
"olden_primal": "オールド・プライマルシリーズ",
|
||||
"beast": "四象武器",
|
||||
"omega": "マグナシリーズ",
|
||||
"regalia": "レガリアシリーズ",
|
||||
"militis": "ミーレスシリーズ",
|
||||
"xeno": "六道武器",
|
||||
"astral": "アストラルウェポン",
|
||||
|
|
@ -101,7 +106,10 @@
|
|||
"vintage": "ヴィンテージシリーズ",
|
||||
"class_champion": "英雄武器",
|
||||
"sephira": "セフィラン・オールドウェポン",
|
||||
"new_world": "新世界の礎"
|
||||
"new_world": "新世界の礎",
|
||||
"revenant": "天星器",
|
||||
"proving": "ブレイブグラウンド",
|
||||
"disaster": "災害シリーズ"
|
||||
},
|
||||
"recency": {
|
||||
"all_time": "全ての期間",
|
||||
|
|
@ -122,6 +130,16 @@
|
|||
"about": {
|
||||
"title": "このサイトについて"
|
||||
},
|
||||
"conflict": {
|
||||
"character": "<strong>同じ名前のキャラクター</strong>がパーティに編成されています。<br />以下のキャラクターを入れ替えますか?",
|
||||
"weapon": {
|
||||
"generic": "<strong>{{series}}</strong>の武器に装備制限があり、武器が既に装備されています。<br /><br />以下の武器を入れ替えますか?",
|
||||
"opus-draconic": "<strong>終末の神器シリーズ</strong>と<strong>ドラコニックシリーズ</strong>から1本しか装備できない制限があり、武器が既に装備されています。<br /><br />以下の武器を入れ替えますか?"
|
||||
},
|
||||
"buttons": {
|
||||
"confirm": "入れ替える"
|
||||
}
|
||||
},
|
||||
"delete_team": {
|
||||
"title": "編成を削除しますか",
|
||||
"description": "編成を削除する操作は取り消せません。",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ class Api {
|
|||
})
|
||||
}
|
||||
|
||||
resolveCharacterConflict({ incoming, conflicting, position, params }: {
|
||||
resolveConflict({ object, incoming, conflicting, position, params }: {
|
||||
object: 'characters' | 'weapons'
|
||||
incoming: string
|
||||
conflicting: string[]
|
||||
position: number,
|
||||
|
|
@ -90,7 +91,7 @@ class Api {
|
|||
position: position,
|
||||
},
|
||||
}
|
||||
const resourceUrl = `${this.url}/characters/resolve`
|
||||
const resourceUrl = `${this.url}/${object}/resolve`
|
||||
return axios.post(resourceUrl, body, { headers: params })
|
||||
}
|
||||
|
||||
|
|
|
|||
4
utils/mapWeaponSeries.tsx
Normal file
4
utils/mapWeaponSeries.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { weaponSeries } from '~utils/weaponSeries'
|
||||
|
||||
export default (id: number) =>
|
||||
weaponSeries.find((series) => series.id === id)?.slug
|
||||
119
utils/weaponSeries.tsx
Normal file
119
utils/weaponSeries.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
export interface WeaponSeries {
|
||||
id: number
|
||||
slug: string
|
||||
}
|
||||
|
||||
export const weaponSeries: WeaponSeries[] = [
|
||||
{
|
||||
id: 0,
|
||||
slug: 'seraphic',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
slug: 'grand',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
slug: 'opus',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
slug: 'draconic',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
slug: 'revenant',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
slug: 'primal',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
slug: 'beast',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
slug: 'regalia',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
slug: 'omega',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
slug: 'olden_primal',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
slug: 'militis',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
slug: 'hollowsky',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
slug: 'xeno',
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
slug: 'astral',
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
slug: 'rose',
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
slug: 'bahamut',
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
slug: 'ultima',
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
slug: 'epic',
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
slug: 'ennead',
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
slug: 'cosmic',
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
slug: 'ancestral',
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
slug: 'superlative',
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
slug: 'vintage',
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
slug: 'class_champion',
|
||||
},
|
||||
{
|
||||
id: 25,
|
||||
slug: 'proving',
|
||||
},
|
||||
{
|
||||
id: 28,
|
||||
slug: 'sephira',
|
||||
},
|
||||
{
|
||||
id: 29,
|
||||
slug: 'new_world',
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
slug: 'disaster',
|
||||
},
|
||||
]
|
||||
Loading…
Reference in a new issue