Merge pull request #147 from jedmund/character-mods
Add character mods and context menus
This commit is contained in:
commit
17addbcba6
77 changed files with 2911 additions and 856 deletions
|
|
@ -1,13 +1,14 @@
|
||||||
.Account.Dialog {
|
.Account.DialogContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 2;
|
gap: $unit-2x;
|
||||||
width: $unit * 64;
|
width: $unit * 64;
|
||||||
|
|
||||||
form {
|
.Fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 2;
|
gap: $unit-2x;
|
||||||
|
padding: 0 $unit-4x;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DialogDescription {
|
.DialogDescription {
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ import React, { useEffect, useState } from 'react'
|
||||||
import { getCookie, setCookie } from 'cookies-next'
|
import { getCookie, setCookie } from 'cookies-next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from '~components/Dialog'
|
} from '~components/Dialog'
|
||||||
|
import DialogContent from '~components/DialogContent'
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import SelectItem from '~components/SelectItem'
|
import SelectItem from '~components/SelectItem'
|
||||||
import PictureSelectItem from '~components/PictureSelectItem'
|
import PictureSelectItem from '~components/PictureSelectItem'
|
||||||
|
|
@ -23,7 +24,6 @@ import { pictureData } from '~utils/pictureData'
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import { useTheme } from 'next-themes'
|
|
||||||
|
|
||||||
type StateVariables = {
|
type StateVariables = {
|
||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
|
|
@ -285,7 +285,7 @@ const AccountModal = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Account Dialog"
|
className="Account"
|
||||||
onOpenAutoFocus={(event: Event) => {}}
|
onOpenAutoFocus={(event: Event) => {}}
|
||||||
onEscapeKeyDown={onEscapeKeyDown}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
>
|
>
|
||||||
|
|
@ -304,14 +304,18 @@ const AccountModal = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={update}>
|
<form onSubmit={update}>
|
||||||
|
<div className="Fields">
|
||||||
{pictureField()}
|
{pictureField()}
|
||||||
{genderField()}
|
{genderField()}
|
||||||
{languageField()}
|
{languageField()}
|
||||||
{themeField()}
|
{themeField()}
|
||||||
|
</div>
|
||||||
|
<div className="DialogFooter">
|
||||||
<Button
|
<Button
|
||||||
contained={true}
|
contained={true}
|
||||||
text={t('modals.settings.buttons.confirm')}
|
text={t('modals.settings.buttons.confirm')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,31 @@
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 21;
|
z-index: 31;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Alert {
|
.Alert {
|
||||||
background: $grey-100;
|
background: var(--dialog-bg);
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit;
|
gap: $unit-2x;
|
||||||
min-width: $unit * 20;
|
min-width: 20vw;
|
||||||
max-width: $unit * 40;
|
max-width: 30vw;
|
||||||
padding: $unit * 4;
|
padding: $unit * 4;
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
font-size: $font-regular;
|
font-size: $font-regular;
|
||||||
line-height: 1.26;
|
line-height: 1.4;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
|
display: flex;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
gap: $unit;
|
||||||
|
|
||||||
.Button {
|
|
||||||
font-size: $font-regular;
|
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
|
||||||
margin-top: $unit * 2;
|
|
||||||
|
|
||||||
&.btn-disabled {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-70;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-50;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
|
import Overlay from '~components/Overlay'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean
|
open: boolean
|
||||||
title?: string
|
title?: string
|
||||||
message: string
|
message: string | React.ReactNode
|
||||||
primaryAction?: () => void
|
primaryAction?: () => void
|
||||||
primaryActionText?: string
|
primaryActionText?: string
|
||||||
cancelAction: () => void
|
cancelAction: () => void
|
||||||
|
|
@ -29,13 +30,18 @@ const Alert = (props: Props) => {
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
<AlertDialog.Cancel asChild>
|
<AlertDialog.Cancel asChild>
|
||||||
<Button
|
<Button
|
||||||
|
contained={true}
|
||||||
onClick={props.cancelAction}
|
onClick={props.cancelAction}
|
||||||
text={props.cancelActionText}
|
text={props.cancelActionText}
|
||||||
/>
|
/>
|
||||||
</AlertDialog.Cancel>
|
</AlertDialog.Cancel>
|
||||||
{props.primaryAction ? (
|
{props.primaryAction ? (
|
||||||
<AlertDialog.Action onClick={props.primaryAction}>
|
<AlertDialog.Action asChild>
|
||||||
{props.primaryActionText}
|
<Button
|
||||||
|
contained={true}
|
||||||
|
onClick={props.primaryAction}
|
||||||
|
text={props.primaryActionText}
|
||||||
|
/>
|
||||||
</AlertDialog.Action>
|
</AlertDialog.Action>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
|
@ -43,6 +49,7 @@ const Alert = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</AlertDialog.Content>
|
</AlertDialog.Content>
|
||||||
</div>
|
</div>
|
||||||
|
<Overlay open={props.open} visible={true} />
|
||||||
</AlertDialog.Portal>
|
</AlertDialog.Portal>
|
||||||
</AlertDialog.Root>
|
</AlertDialog.Root>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,199 +1,95 @@
|
||||||
import React, { ForwardedRef, useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
|
|
||||||
import Input from '~components/LabelledInput'
|
import SelectWithInput from '~components/SelectWithInput'
|
||||||
import Select from '~components/Select'
|
import { weaponAwakening, characterAwakening } from '~data/awakening'
|
||||||
import SelectItem from '~components/SelectItem'
|
|
||||||
|
|
||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
import { weaponAwakening, characterAwakening } from '~utils/awakening'
|
|
||||||
import type { Awakening } from '~utils/awakening'
|
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
object: 'character' | 'weapon'
|
object: 'character' | 'weapon'
|
||||||
awakeningType?: number
|
type?: number
|
||||||
awakeningLevel?: number
|
level?: number
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange?: (open: boolean) => void
|
||||||
sendValidity: (isValid: boolean) => void
|
sendValidity: (isValid: boolean) => void
|
||||||
sendValues: (type: number, level: number) => void
|
sendValues: (type: number, level: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AwakeningSelect = (props: Props) => {
|
const AwakeningSelect = (props: Props) => {
|
||||||
const router = useRouter()
|
// Data states
|
||||||
const locale =
|
const [awakeningType, setAwakeningType] = useState(
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
props.object === 'weapon' ? 0 : 1
|
||||||
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 [awakeningLevel, setAwakeningLevel] = useState(1)
|
||||||
|
|
||||||
const [maxValue, setMaxValue] = useState(1)
|
// Data
|
||||||
|
const chooseDataset = () => {
|
||||||
|
let list: ItemSkill[] = []
|
||||||
|
|
||||||
const [error, setError] = useState('')
|
switch (props.object) {
|
||||||
|
case 'character':
|
||||||
// Classes
|
list = characterAwakening
|
||||||
const inputClasses = classNames({
|
break
|
||||||
Bound: true,
|
case 'weapon':
|
||||||
Hidden: awakeningType === -1,
|
// WARNING: Clonedeep is masking a deeper error
|
||||||
|
// which is running this method every time this component is rerendered
|
||||||
|
// causing multiple "No awakening" items to be added
|
||||||
|
const awakening = cloneDeep(weaponAwakening)
|
||||||
|
awakening.unshift({
|
||||||
|
id: 0,
|
||||||
|
name: {
|
||||||
|
en: 'No awakening',
|
||||||
|
ja: '覚醒なし',
|
||||||
|
},
|
||||||
|
slug: 'no-awakening',
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 0,
|
||||||
|
fractional: false,
|
||||||
})
|
})
|
||||||
|
list = awakening
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const errorClasses = classNames({
|
return list
|
||||||
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
|
// Set default awakening and level based on object type
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let defaultAwakening = 0
|
const defaultAwakening = props.object === 'weapon' ? 0 : 1
|
||||||
if (props.object === 'weapon') defaultAwakening = -1
|
const type = props.type != undefined ? props.type : defaultAwakening
|
||||||
|
|
||||||
setAwakeningType(
|
setAwakeningType(type)
|
||||||
props.awakeningType != undefined ? props.awakeningType : defaultAwakening
|
setAwakeningLevel(props.level ? props.level : 1)
|
||||||
)
|
}, [props.object, props.type, props.level])
|
||||||
setAwakeningLevel(props.awakeningLevel ? props.awakeningLevel : 1)
|
|
||||||
}, [props.object, props.awakeningType, props.awakeningLevel])
|
|
||||||
|
|
||||||
// Send awakening type and level when changed
|
|
||||||
useEffect(() => {
|
|
||||||
props.sendValues(awakeningType, awakeningLevel)
|
|
||||||
}, [props.sendValues, awakeningType, awakeningLevel])
|
|
||||||
|
|
||||||
// Send validity of form when awakening level changes
|
// Send validity of form when awakening level changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.sendValidity(awakeningLevel > 0 && error === '')
|
props.sendValidity(awakeningLevel > 0)
|
||||||
}, [props.sendValidity, awakeningLevel, error])
|
}, [props.sendValidity, awakeningLevel])
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
function changeOpen() {
|
function changeOpen(open: boolean) {
|
||||||
setOpen(!open)
|
if (props.onOpenChange) props.onOpenChange(open)
|
||||||
props.onOpenChange(!open)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClose() {
|
function handleValueChange(type: number, level: number) {
|
||||||
props.onOpenChange(false)
|
setAwakeningType(type)
|
||||||
}
|
setAwakeningLevel(level)
|
||||||
|
props.sendValues(type, level)
|
||||||
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('awakening.no_type')}
|
|
||||||
</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 % 1 != 0) {
|
|
||||||
error = t('awakening.errors.value_not_whole')
|
|
||||||
} 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 (
|
return (
|
||||||
<div className="AwakeningSelect">
|
<div className="Awakening">
|
||||||
<div className="AwakeningSet">
|
<SelectWithInput
|
||||||
<div className="fields">
|
object={`${props.object}_awakening`}
|
||||||
<Select
|
dataSet={chooseDataset()}
|
||||||
key="awakening_type"
|
selectValue={awakeningType}
|
||||||
value={`${awakeningType}`}
|
inputValue={awakeningLevel}
|
||||||
open={open}
|
onOpenChange={changeOpen}
|
||||||
onValueChange={handleSelectChange}
|
sendValidity={props.sendValidity}
|
||||||
onOpenChange={() => changeOpen()}
|
sendValues={handleValueChange}
|
||||||
onClose={onClose}
|
|
||||||
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}
|
|
||||||
visible={awakeningType !== -1 ? true : false}
|
|
||||||
ref={awakeningLevelInput}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className={errorClasses}>{error}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import SelectItem from '~components/SelectItem'
|
||||||
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { axData } from '~utils/axData'
|
import ax from '~data/ax'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ const AXSelect = (props: Props) => {
|
||||||
|
|
||||||
if (props.currentSkills[0].modifier > -1 && primaryAxValueInput.current) {
|
if (props.currentSkills[0].modifier > -1 && primaryAxValueInput.current) {
|
||||||
const modifier = props.currentSkills[0].modifier
|
const modifier = props.currentSkills[0].modifier
|
||||||
const axSkill = axData[props.axType - 1][modifier]
|
const axSkill = ax[props.axType - 1][modifier]
|
||||||
setupInput(axSkill, primaryAxValueInput.current)
|
setupInput(axSkill, primaryAxValueInput.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ const AXSelect = (props: Props) => {
|
||||||
props.currentSkills[1].modifier != null
|
props.currentSkills[1].modifier != null
|
||||||
) {
|
) {
|
||||||
const firstSkill = props.currentSkills[0]
|
const firstSkill = props.currentSkills[0]
|
||||||
const primaryAxSkill = axData[props.axType - 1][firstSkill.modifier]
|
const primaryAxSkill = ax[props.axType - 1][firstSkill.modifier]
|
||||||
const secondaryAxSkill = findSecondaryAxSkill(
|
const secondaryAxSkill = findSecondaryAxSkill(
|
||||||
primaryAxSkill,
|
primaryAxSkill,
|
||||||
props.currentSkills[1]
|
props.currentSkills[1]
|
||||||
|
|
@ -185,7 +185,7 @@ const AXSelect = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function findSecondaryAxSkill(
|
function findSecondaryAxSkill(
|
||||||
axSkill: AxSkill | undefined,
|
axSkill: ItemSkill | undefined,
|
||||||
skillAtIndex: SimpleAxSkill
|
skillAtIndex: SimpleAxSkill
|
||||||
) {
|
) {
|
||||||
if (axSkill)
|
if (axSkill)
|
||||||
|
|
@ -213,7 +213,7 @@ const AXSelect = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateOptions(modifierSet: number) {
|
function generateOptions(modifierSet: number) {
|
||||||
const axOptions = axData[props.axType - 1]
|
const axOptions = ax[props.axType - 1]
|
||||||
|
|
||||||
let axOptionElements: React.ReactNode[] = []
|
let axOptionElements: React.ReactNode[] = []
|
||||||
if (modifierSet == 0) {
|
if (modifierSet == 0) {
|
||||||
|
|
@ -264,7 +264,7 @@ const AXSelect = (props: Props) => {
|
||||||
secondaryAxModifierSelect.current &&
|
secondaryAxModifierSelect.current &&
|
||||||
secondaryAxValueInput.current
|
secondaryAxValueInput.current
|
||||||
) {
|
) {
|
||||||
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
setupInput(ax[props.axType - 1][value], primaryAxValueInput.current)
|
||||||
setPrimaryAxValue(0)
|
setPrimaryAxValue(0)
|
||||||
primaryAxValueInput.current.value = ''
|
primaryAxValueInput.current.value = ''
|
||||||
|
|
||||||
|
|
@ -280,7 +280,7 @@ const AXSelect = (props: Props) => {
|
||||||
const value = parseInt(rawValue)
|
const value = parseInt(rawValue)
|
||||||
setSecondaryAxModifier(value)
|
setSecondaryAxModifier(value)
|
||||||
|
|
||||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
const primaryAxSkill = ax[props.axType - 1][primaryAxModifier]
|
||||||
const currentAxSkill = primaryAxSkill.secondary
|
const currentAxSkill = primaryAxSkill.secondary
|
||||||
? primaryAxSkill.secondary.find((skill) => skill.id == value)
|
? primaryAxSkill.secondary.find((skill) => skill.id == value)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
@ -304,7 +304,7 @@ const AXSelect = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePrimaryErrors(value: number) {
|
function handlePrimaryErrors(value: number) {
|
||||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
const primaryAxSkill = ax[props.axType - 1][primaryAxModifier]
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
if (value < primaryAxSkill.minValue) {
|
if (value < primaryAxSkill.minValue) {
|
||||||
|
|
@ -333,7 +333,7 @@ const AXSelect = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSecondaryErrors(value: number) {
|
function handleSecondaryErrors(value: number) {
|
||||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
const primaryAxSkill = ax[props.axType - 1][primaryAxModifier]
|
||||||
let newErrors = { ...errors }
|
let newErrors = { ...errors }
|
||||||
|
|
||||||
if (primaryAxSkill.secondary) {
|
if (primaryAxSkill.secondary) {
|
||||||
|
|
@ -373,7 +373,7 @@ const AXSelect = (props: Props) => {
|
||||||
return newErrors.axValue2.length === 0
|
return newErrors.axValue2.length === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
|
function setupInput(ax: ItemSkill | undefined, element: HTMLInputElement) {
|
||||||
if (ax) {
|
if (ax) {
|
||||||
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
|
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
font-size: $font-button;
|
font-size: $font-button;
|
||||||
font-weight: $normal;
|
font-weight: $normal;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
transition: 0.18s opacity ease-in-out;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.Blended:hover,
|
&.Blended:hover,
|
||||||
|
|
@ -61,6 +62,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Options {
|
||||||
|
box-shadow: 0px 1px 3px rgb(0 0 0 / 14%);
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
top: 8px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: var(--button-bg-disabled);
|
background-color: var(--button-bg-disabled);
|
||||||
color: var(--button-text-disabled);
|
color: var(--button-text-disabled);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Trans, useTranslation } from 'next-i18next'
|
import { Trans, useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import { Dialog, DialogContent } from '~components/Dialog'
|
import { Dialog } from '~components/Dialog'
|
||||||
|
import DialogContent from '~components/DialogContent'
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Overlay from '~components/Overlay'
|
import Overlay from '~components/Overlay'
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@ const CharacterConflictModal = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Conflict Dialog"
|
className="Conflict"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
onEscapeKeyDown={close}
|
onEscapeKeyDown={close}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,15 @@ const CharacterGrid = (props: Props) => {
|
||||||
setIncoming(undefined)
|
setIncoming(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeCharacter(id: string) {
|
||||||
|
try {
|
||||||
|
const response = await api.endpoints.grid_characters.destroy({ id: id })
|
||||||
|
appState.grid.characters[response.data.position] = undefined
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Saving job and job skills
|
// Methods: Saving job and job skills
|
||||||
const saveJob = async function (job?: Job) {
|
const saveJob = async function (job?: Job) {
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
@ -371,6 +380,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
position={i}
|
position={i}
|
||||||
updateObject={receiveCharacterFromSearch}
|
updateObject={receiveCharacterFromSearch}
|
||||||
updateUncap={initiateUncapUpdate}
|
updateUncap={initiateUncapUpdate}
|
||||||
|
removeCharacter={removeCharacter}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
78
components/CharacterModal/index.scss
Normal file
78
components/CharacterModal/index.scss
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
.Character.DialogContent {
|
||||||
|
gap: $unit;
|
||||||
|
min-width: 480px;
|
||||||
|
|
||||||
|
@include breakpoint(phone) {
|
||||||
|
min-width: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
transition: 0.18s padding-top ease-in-out;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&.Scrolled {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.34);
|
||||||
|
padding-top: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
transition: 0.2s width ease-in-out;
|
||||||
|
width: $unit-6x !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTitle {
|
||||||
|
font-size: $font-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SubTitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mods {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-4x;
|
||||||
|
padding: 0 $unit-4x;
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
&.inline {
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: $grey-55;
|
||||||
|
font-size: $font-small;
|
||||||
|
margin-bottom: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: $grey-90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: ($unit * 1.5) ($unit-2x);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.btn-disabled {
|
||||||
|
background: $grey-90;
|
||||||
|
color: $grey-70;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
327
components/CharacterModal/index.tsx
Normal file
327
components/CharacterModal/index.tsx
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
// Core dependencies
|
||||||
|
import React, {
|
||||||
|
PropsWithChildren,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { AxiosResponse } from 'axios'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
// UI dependencies
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '~components/Dialog'
|
||||||
|
import DialogContent from '~components/DialogContent'
|
||||||
|
import Button from '~components/Button'
|
||||||
|
import SelectWithInput from '~components/SelectWithInput'
|
||||||
|
import AwakeningSelect from '~components/AwakeningSelect'
|
||||||
|
import RingSelect from '~components/RingSelect'
|
||||||
|
import Switch from '~components/Switch'
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
import api from '~utils/api'
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
|
import { retrieveCookies } from '~utils/retrieveCookies'
|
||||||
|
import elementalizeAetherialMastery from '~utils/elementalizeAetherialMastery'
|
||||||
|
|
||||||
|
// Data
|
||||||
|
const emptyExtendedMastery: ExtendedMastery = {
|
||||||
|
modifier: 0,
|
||||||
|
strength: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles and icons
|
||||||
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { CharacterOverMastery, ExtendedMastery } from '~types'
|
||||||
|
|
||||||
|
interface GridCharacterObject {
|
||||||
|
character: {
|
||||||
|
ring1: ExtendedMastery
|
||||||
|
ring2: ExtendedMastery
|
||||||
|
ring3: ExtendedMastery
|
||||||
|
ring4: ExtendedMastery
|
||||||
|
earring: ExtendedMastery
|
||||||
|
awakening: {
|
||||||
|
type?: number
|
||||||
|
level?: number
|
||||||
|
}
|
||||||
|
transcendence_step: number
|
||||||
|
perpetuity: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
gridCharacter: GridCharacter
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CharacterModal = ({
|
||||||
|
gridCharacter,
|
||||||
|
children,
|
||||||
|
open: modalOpen,
|
||||||
|
onOpenChange,
|
||||||
|
}: PropsWithChildren<Props>) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
// Cookies
|
||||||
|
const cookies = retrieveCookies()
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [formValid, setFormValid] = useState(false)
|
||||||
|
|
||||||
|
// Classes
|
||||||
|
const headerClasses = classNames({
|
||||||
|
DialogHeader: true,
|
||||||
|
Scrolled: scrolled,
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(modalOpen)
|
||||||
|
}, [modalOpen])
|
||||||
|
|
||||||
|
// Character properties: Perpetuity
|
||||||
|
const [perpetuity, setPerpetuity] = useState(false)
|
||||||
|
|
||||||
|
// Character properties: Ring
|
||||||
|
const [rings, setRings] = useState<CharacterOverMastery>({
|
||||||
|
1: { ...emptyExtendedMastery, modifier: 1 },
|
||||||
|
2: { ...emptyExtendedMastery, modifier: 2 },
|
||||||
|
3: emptyExtendedMastery,
|
||||||
|
4: emptyExtendedMastery,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Character properties: Earrings
|
||||||
|
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
|
||||||
|
|
||||||
|
// Character properties: Awakening
|
||||||
|
const [awakeningType, setAwakeningType] = useState(0)
|
||||||
|
const [awakeningLevel, setAwakeningLevel] = useState(0)
|
||||||
|
|
||||||
|
// Character properties: Transcendence
|
||||||
|
const [transcendenceStep, setTranscendenceStep] = useState(0)
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
useEffect(() => {
|
||||||
|
if (gridCharacter.aetherial_mastery) {
|
||||||
|
setEarring({
|
||||||
|
modifier: gridCharacter.aetherial_mastery.modifier,
|
||||||
|
strength: gridCharacter.aetherial_mastery.strength,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setAwakeningType(gridCharacter.awakening.type)
|
||||||
|
setAwakeningLevel(gridCharacter.awakening.level)
|
||||||
|
setPerpetuity(gridCharacter.perpetuity)
|
||||||
|
}, [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
|
||||||
|
function prepareObject() {
|
||||||
|
let object: GridCharacterObject = {
|
||||||
|
character: {
|
||||||
|
ring1: {
|
||||||
|
modifier: rings[1].modifier,
|
||||||
|
strength: rings[1].strength,
|
||||||
|
},
|
||||||
|
ring2: {
|
||||||
|
modifier: rings[2].modifier,
|
||||||
|
strength: rings[2].strength,
|
||||||
|
},
|
||||||
|
ring3: {
|
||||||
|
modifier: rings[3].modifier,
|
||||||
|
strength: rings[3].strength,
|
||||||
|
},
|
||||||
|
ring4: {
|
||||||
|
modifier: rings[4].modifier,
|
||||||
|
strength: rings[4].strength,
|
||||||
|
},
|
||||||
|
earring: {
|
||||||
|
modifier: earring.modifier,
|
||||||
|
strength: earring.strength,
|
||||||
|
},
|
||||||
|
awakening: {
|
||||||
|
type: awakeningType,
|
||||||
|
level: awakeningLevel,
|
||||||
|
},
|
||||||
|
transcendence_step: transcendenceStep,
|
||||||
|
perpetuity: perpetuity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the GridWeaponObject to the server
|
||||||
|
async function updateCharacter() {
|
||||||
|
const updateObject = prepareObject()
|
||||||
|
|
||||||
|
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
|
||||||
|
function processResult(response: AxiosResponse) {
|
||||||
|
const gridCharacter: GridCharacter = response.data
|
||||||
|
appState.grid.characters[gridCharacter.position] = gridCharacter
|
||||||
|
|
||||||
|
setOpen(false)
|
||||||
|
if (onOpenChange) onOpenChange(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processError(error: any) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ringSelect = () => {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h3>{t('modals.characters.subtitles.ring')}</h3>
|
||||||
|
<RingSelect
|
||||||
|
gridCharacter={gridCharacter}
|
||||||
|
sendValues={receiveRingValues}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const earringSelect = () => {
|
||||||
|
const earringData = elementalizeAetherialMastery(gridCharacter)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h3>{t('modals.characters.subtitles.earring')}</h3>
|
||||||
|
<SelectWithInput
|
||||||
|
object="earring"
|
||||||
|
dataSet={earringData}
|
||||||
|
selectValue={earring.modifier ? earring.modifier : 0}
|
||||||
|
inputValue={earring.strength ? earring.strength : 0}
|
||||||
|
sendValidity={receiveValidity}
|
||||||
|
sendValues={receiveEarringValues}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const awakeningSelect = () => {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h3>{t('modals.characters.subtitles.awakening')}</h3>
|
||||||
|
<AwakeningSelect
|
||||||
|
object="character"
|
||||||
|
type={awakeningType}
|
||||||
|
level={awakeningLevel}
|
||||||
|
sendValidity={receiveValidity}
|
||||||
|
sendValues={receiveAwakeningValues}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const perpetuitySwitch = () => {
|
||||||
|
return (
|
||||||
|
<section className="inline">
|
||||||
|
<h3>{t('modals.characters.subtitles.permanent')}</h3>
|
||||||
|
<Switch onCheckedChange={handleCheckedChange} checked={perpetuity} />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
|
<DialogContent
|
||||||
|
className="Character"
|
||||||
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
|
onEscapeKeyDown={() => {}}
|
||||||
|
>
|
||||||
|
<div className={headerClasses}>
|
||||||
|
<img
|
||||||
|
alt={gridCharacter.object.name[locale]}
|
||||||
|
className="DialogImage"
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-square/${gridCharacter.object.granblue_id}_01.jpg`}
|
||||||
|
/>
|
||||||
|
<div className="DialogTop">
|
||||||
|
<DialogTitle className="SubTitle">
|
||||||
|
{t('modals.characters.title')}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogTitle className="DialogTitle">
|
||||||
|
{gridCharacter.object.name[locale]}
|
||||||
|
</DialogTitle>
|
||||||
|
</div>
|
||||||
|
<DialogClose className="DialogClose" asChild>
|
||||||
|
<span>
|
||||||
|
<CrossIcon />
|
||||||
|
</span>
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mods">
|
||||||
|
{perpetuitySwitch()}
|
||||||
|
{ringSelect()}
|
||||||
|
{earringSelect()}
|
||||||
|
{awakeningSelect()}
|
||||||
|
</div>
|
||||||
|
<div className="DialogFooter">
|
||||||
|
<Button
|
||||||
|
contained={true}
|
||||||
|
onClick={updateCharacter}
|
||||||
|
disabled={!formValid}
|
||||||
|
text={t('modals.characters.buttons.confirm')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CharacterModal
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
// min-height: 320px;
|
// min-height: 320px;
|
||||||
// max-width: 200px;
|
// max-width: 200px;
|
||||||
|
position: relative;
|
||||||
margin-bottom: $unit * 4;
|
margin-bottom: $unit * 4;
|
||||||
|
|
||||||
&.editable .CharacterImage:hover {
|
&.editable .CharacterImage:hover {
|
||||||
|
|
@ -22,6 +23,17 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .Button,
|
||||||
|
.Button.Clicked {
|
||||||
|
pointer-events: initial;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
h3,
|
h3,
|
||||||
ul {
|
ul {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -57,9 +69,11 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.18s ease-in-out;
|
transition: $duration-zoom all ease-in-out;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&:hover .icon svg {
|
&:hover .icon svg {
|
||||||
fill: var(--icon-secondary-hover);
|
fill: var(--icon-secondary-hover);
|
||||||
|
|
@ -72,6 +86,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
transition: $duration-color-fade fill ease-in-out;
|
||||||
fill: var(--icon-secondary);
|
fill: var(--icon-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
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 { useTranslation } from 'next-i18next'
|
import { Trans, useTranslation } from 'next-i18next'
|
||||||
import classnames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import Alert from '~components/Alert'
|
||||||
|
import Button from '~components/Button'
|
||||||
|
import CharacterHovercard from '~components/CharacterHovercard'
|
||||||
|
import CharacterModal from '~components/CharacterModal'
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
ContextMenuContent,
|
||||||
|
} from '~components/ContextMenu'
|
||||||
|
import ContextMenuItem from '~components/ContextMenuItem'
|
||||||
|
import SearchModal from '~components/SearchModal'
|
||||||
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import CharacterHovercard from '~components/CharacterHovercard'
|
|
||||||
import SearchModal from '~components/SearchModal'
|
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
|
||||||
import PlusIcon from '~public/icons/Add.svg'
|
import PlusIcon from '~public/icons/Add.svg'
|
||||||
|
import SettingsIcon from '~public/icons/Settings.svg'
|
||||||
|
|
||||||
import type { SearchableObject } from '~types'
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
|
|
@ -19,48 +30,112 @@ interface Props {
|
||||||
gridCharacter?: GridCharacter
|
gridCharacter?: GridCharacter
|
||||||
position: number
|
position: number
|
||||||
editable: boolean
|
editable: boolean
|
||||||
|
removeCharacter: (id: string) => void
|
||||||
updateObject: (object: SearchableObject, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterUnit = (props: Props) => {
|
const CharacterUnit = ({
|
||||||
|
gridCharacter,
|
||||||
|
position,
|
||||||
|
editable,
|
||||||
|
removeCharacter: sendCharacterToRemove,
|
||||||
|
updateObject,
|
||||||
|
updateUncap,
|
||||||
|
}: Props) => {
|
||||||
|
// Translations and locale
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const { party, grid } = useSnapshot(appState)
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
|
// State snapshot
|
||||||
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
|
||||||
|
// 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('')
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
|
|
||||||
const classes = classnames({
|
// Classes
|
||||||
|
const classes = classNames({
|
||||||
CharacterUnit: true,
|
CharacterUnit: true,
|
||||||
editable: props.editable,
|
editable: editable,
|
||||||
filled: props.gridCharacter !== undefined,
|
filled: gridCharacter !== undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridCharacter = props.gridCharacter
|
const buttonClasses = classNames({
|
||||||
|
Options: true,
|
||||||
|
Clicked: contextMenuOpen,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Other
|
||||||
const character = gridCharacter?.object
|
const character = gridCharacter?.object
|
||||||
|
|
||||||
|
// Hooks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateImageUrl()
|
generateImageUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Methods: Open layer
|
||||||
|
function openCharacterModal(event: Event) {
|
||||||
|
setDetailsModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
|
||||||
|
if (editable) setSearchModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRemoveCharacterAlert() {
|
||||||
|
setAlertOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Handle button clicked
|
||||||
|
function handleButtonClicked() {
|
||||||
|
setContextMenuOpen(!contextMenuOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Handle open change
|
||||||
|
function handleCharacterModalOpenChange(open: boolean) {
|
||||||
|
setDetailsModalOpen(open)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearchModalOpenChange(open: boolean) {
|
||||||
|
setSearchModalOpen(open)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleContextMenuOpenChange(open: boolean) {
|
||||||
|
if (!open) setContextMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Mutate data
|
||||||
|
function passUncapData(uncap: number) {
|
||||||
|
if (gridCharacter) updateUncap(gridCharacter.id, position, uncap)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCharacter() {
|
||||||
|
if (gridCharacter) sendCharacterToRemove(gridCharacter.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Image string generation
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ''
|
let imgSrc = ''
|
||||||
|
|
||||||
if (props.gridCharacter) {
|
if (gridCharacter) {
|
||||||
const character = props.gridCharacter.object!
|
const character = gridCharacter.object!
|
||||||
|
|
||||||
// Change the image based on the uncap level
|
// Change the image based on the uncap level
|
||||||
let suffix = '01'
|
let suffix = '01'
|
||||||
if (props.gridCharacter.uncap_level == 6) suffix = '04'
|
if (gridCharacter.uncap_level == 6) suffix = '04'
|
||||||
else if (props.gridCharacter.uncap_level == 5) suffix = '03'
|
else if (gridCharacter.uncap_level == 5) suffix = '03'
|
||||||
else if (props.gridCharacter.uncap_level > 2) suffix = '02'
|
else if (gridCharacter.uncap_level > 2) suffix = '02'
|
||||||
|
|
||||||
// Special casing for Lyria (and Young Cat eventually)
|
// Special casing for Lyria (and Young Cat eventually)
|
||||||
if (props.gridCharacter.object.granblue_id === '3030182000') {
|
if (gridCharacter.object.granblue_id === '3030182000') {
|
||||||
let element = 1
|
let element = 1
|
||||||
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
||||||
element = grid.weapons.mainWeapon.element
|
element = grid.weapons.mainWeapon.element
|
||||||
|
|
@ -77,15 +152,86 @@ const CharacterUnit = (props: Props) => {
|
||||||
setImageUrl(imgSrc)
|
setImageUrl(imgSrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
function passUncapData(uncap: number) {
|
// Methods: Layer element rendering
|
||||||
if (props.gridCharacter)
|
const characterModal = () => {
|
||||||
props.updateUncap(props.gridCharacter.id, props.position, uncap)
|
if (gridCharacter) {
|
||||||
|
return (
|
||||||
|
<CharacterModal
|
||||||
|
gridCharacter={gridCharacter}
|
||||||
|
open={detailsModalOpen}
|
||||||
|
onOpenChange={handleCharacterModalOpenChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contextMenu = () => {
|
||||||
|
if (editable && gridCharacter && gridCharacter.id) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContextMenu onOpenChange={handleContextMenuOpenChange}>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
accessoryIcon={<SettingsIcon />}
|
||||||
|
className={buttonClasses}
|
||||||
|
onClick={handleButtonClicked}
|
||||||
|
/>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent align="start">
|
||||||
|
<ContextMenuItem onSelect={openCharacterModal}>
|
||||||
|
{t('context.modify.character')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem onSelect={openRemoveCharacterAlert}>
|
||||||
|
{t('context.remove')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
|
{characterModal()}
|
||||||
|
{removeAlert()}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeAlert = () => {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
open={alertOpen}
|
||||||
|
primaryAction={removeCharacter}
|
||||||
|
primaryActionText={t('modals.characters.buttons.remove')}
|
||||||
|
cancelAction={() => setAlertOpen(false)}
|
||||||
|
cancelActionText={t('buttons.cancel')}
|
||||||
|
message={
|
||||||
|
<Trans i18nKey="modals.characters.messages.remove">
|
||||||
|
Are you sure you want to remove{' '}
|
||||||
|
<strong>{{ character: gridCharacter?.object.name[locale] }}</strong>{' '}
|
||||||
|
from your team?
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchModal = () => {
|
||||||
|
if (editable) {
|
||||||
|
return (
|
||||||
|
<SearchModal
|
||||||
|
placeholderText={t('search.placeholders.character')}
|
||||||
|
fromPosition={position}
|
||||||
|
object="characters"
|
||||||
|
open={searchModalOpen}
|
||||||
|
onOpenChange={handleSearchModalOpenChange}
|
||||||
|
send={updateObject}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Core element rendering
|
||||||
const image = (
|
const image = (
|
||||||
<div className="CharacterImage">
|
<div className="CharacterImage" onClick={openSearchModal}>
|
||||||
<img alt={character?.name.en} className="grid_image" src={imageUrl} />
|
<img alt={character?.name.en} className="grid_image" src={imageUrl} />
|
||||||
{props.editable ? (
|
{editable ? (
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -95,20 +241,11 @@ const CharacterUnit = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const editableImage = (
|
|
||||||
<SearchModal
|
|
||||||
placeholderText={t('search.placeholders.character')}
|
|
||||||
fromPosition={props.position}
|
|
||||||
object="characters"
|
|
||||||
send={props.updateObject}
|
|
||||||
>
|
|
||||||
{image}
|
|
||||||
</SearchModal>
|
|
||||||
)
|
|
||||||
|
|
||||||
const unitContent = (
|
const unitContent = (
|
||||||
|
<>
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{props.editable ? editableImage : image}
|
{contextMenu()}
|
||||||
|
{image}
|
||||||
{gridCharacter && character ? (
|
{gridCharacter && character ? (
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="character"
|
type="character"
|
||||||
|
|
@ -123,15 +260,17 @@ const CharacterUnit = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
{searchModal()}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const withHovercard = (
|
const unitContentWithHovercard = (
|
||||||
<CharacterHovercard gridCharacter={gridCharacter!}>
|
<CharacterHovercard gridCharacter={gridCharacter!}>
|
||||||
{unitContent}
|
{unitContent}
|
||||||
</CharacterHovercard>
|
</CharacterHovercard>
|
||||||
)
|
)
|
||||||
|
|
||||||
return gridCharacter && !props.editable ? withHovercard : unitContent
|
return gridCharacter && !editable ? unitContentWithHovercard : unitContent
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterUnit
|
export default CharacterUnit
|
||||||
|
|
|
||||||
6
components/ContextMenu/index.scss
Normal file
6
components/ContextMenu/index.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.ContextMenu {
|
||||||
|
background: var(--menu-bg);
|
||||||
|
border-radius: $input-corner;
|
||||||
|
padding: $unit 0;
|
||||||
|
margin-top: $unit-fourth;
|
||||||
|
}
|
||||||
36
components/ContextMenu/index.tsx
Normal file
36
components/ContextMenu/index.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
extends React.DetailedHTMLProps<
|
||||||
|
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> {
|
||||||
|
align?: 'start' | 'center' | 'end'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContextMenuContent = React.forwardRef<HTMLDivElement, Props>(
|
||||||
|
function ContextMenu({ children, ...props }, forwardedRef) {
|
||||||
|
const classes = classNames(
|
||||||
|
{
|
||||||
|
ContextMenu: true,
|
||||||
|
},
|
||||||
|
props.className
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content className={classes} {...props} ref={forwardedRef}>
|
||||||
|
{children}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ContextMenu = DropdownMenu.Root
|
||||||
|
export const ContextMenuGroup = DropdownMenu.Group
|
||||||
|
export const ContextMenuTrigger = DropdownMenu.Trigger
|
||||||
11
components/ContextMenuItem/index.scss
Normal file
11
components/ContextMenuItem/index.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
.ContextItem {
|
||||||
|
color: var(--menu-text);
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: ($unit * 1.5) $unit-2x;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--menu-bg-item-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
components/ContextMenuItem/index.tsx
Normal file
30
components/ContextMenuItem/index.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { DropdownMenuItem } from '@radix-ui/react-dropdown-menu'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string
|
||||||
|
onSelect?: (event: Event) => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextMenuItem = React.forwardRef<HTMLDivElement, Props>(
|
||||||
|
function ContextMenu({ children, ...props }, forwardedRef) {
|
||||||
|
const classes = classNames(
|
||||||
|
{
|
||||||
|
ContextItem: true,
|
||||||
|
},
|
||||||
|
props.className
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem className={classes} onSelect={props.onSelect}>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ContextMenuItem
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
.Dialog {
|
|
||||||
$multiplier: 4;
|
|
||||||
|
|
||||||
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
|
|
||||||
openModalDesktop;
|
|
||||||
background: var(--dialog-bg);
|
|
||||||
border-radius: $card-corner;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit * $multiplier;
|
|
||||||
height: auto;
|
|
||||||
min-width: $unit * 48;
|
|
||||||
min-height: $unit-12x;
|
|
||||||
min-width: 580px;
|
|
||||||
padding: $unit * $multiplier;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 40;
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include breakpoint(phone) {
|
|
||||||
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal forwards running
|
|
||||||
openModalMobile;
|
|
||||||
min-width: inherit;
|
|
||||||
min-height: 80vh;
|
|
||||||
transform: initial;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.DialogHeader {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $unit;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
gap: $unit;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-small;
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.DialogClose {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: $error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: $grey-50;
|
|
||||||
float: right;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.DialogTitle {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: $font-xlarge;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: $font-xlarge;
|
|
||||||
font-weight: $medium;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.DialogTop {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
gap: calc($unit / 2);
|
|
||||||
|
|
||||||
.SubTitle {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: $font-small;
|
|
||||||
font-weight: $medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.DialogDescription {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +1,37 @@
|
||||||
import React from 'react'
|
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||||
import classNames from 'classnames'
|
import { useLockedBody } from 'usehooks-ts'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import Overlay from '~components/Overlay'
|
|
||||||
|
|
||||||
interface Props
|
interface Props extends DialogPrimitive.DialogProps {}
|
||||||
extends React.DetailedHTMLProps<
|
|
||||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
export const Dialog = ({ children, ...props }: PropsWithChildren<Props>) => {
|
||||||
HTMLDivElement
|
const [locked, setLocked] = useLockedBody(false, 'root')
|
||||||
> {
|
const [open, setOpen] = useState(false)
|
||||||
onEscapeKeyDown: (event: KeyboardEvent) => void
|
|
||||||
onOpenAutoFocus: (event: Event) => void
|
useEffect(() => {
|
||||||
|
if (props.open != undefined) {
|
||||||
|
toggleLocked(props.open)
|
||||||
|
setOpen(props.open)
|
||||||
|
}
|
||||||
|
}, [props.open])
|
||||||
|
|
||||||
|
function toggleLocked(open: boolean) {
|
||||||
|
setLocked(open)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
function handleOpenChange(open: boolean) {
|
||||||
function dialog({ children, ...props }, forwardedRef) {
|
if (props.onOpenChange) props.onOpenChange(open)
|
||||||
const classes = classNames(
|
}
|
||||||
{
|
|
||||||
Dialog: true,
|
|
||||||
},
|
|
||||||
props.className
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Portal>
|
<DialogPrimitive.Root open={props.open} onOpenChange={handleOpenChange}>
|
||||||
<DialogPrimitive.Content
|
|
||||||
className={classes}
|
|
||||||
{...props}
|
|
||||||
onOpenAutoFocus={props.onOpenAutoFocus}
|
|
||||||
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
||||||
ref={forwardedRef}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Root>
|
||||||
<Overlay visible={true} open={true} />
|
|
||||||
</DialogPrimitive.Portal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
export const Dialog = DialogPrimitive.Root
|
|
||||||
export const DialogTitle = DialogPrimitive.Title
|
export const DialogTitle = DialogPrimitive.Title
|
||||||
export const DialogTrigger = DialogPrimitive.Trigger
|
export const DialogTrigger = DialogPrimitive.Trigger
|
||||||
export const DialogClose = DialogPrimitive.Close
|
export const DialogClose = DialogPrimitive.Close
|
||||||
|
|
|
||||||
243
components/DialogContent/index.scss
Normal file
243
components/DialogContent/index.scss
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
.Dialog {
|
||||||
|
// animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
|
||||||
|
// openModalDesktop;
|
||||||
|
position: fixed;
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-width: 100vw;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 40;
|
||||||
|
|
||||||
|
.DialogContent {
|
||||||
|
$multiplier: 4;
|
||||||
|
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit * $multiplier;
|
||||||
|
height: auto;
|
||||||
|
min-width: $unit * 48;
|
||||||
|
// min-height: $unit-12x;
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: 80vh;
|
||||||
|
min-width: 580px;
|
||||||
|
// padding: $unit * $multiplier;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include breakpoint(phone) {
|
||||||
|
animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal forwards running
|
||||||
|
openModalMobile;
|
||||||
|
min-width: inherit;
|
||||||
|
min-height: 80vh;
|
||||||
|
transform: initial;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit-2x;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $unit-3x ($unit * $multiplier);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-small;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogImage {
|
||||||
|
border-radius: $input-corner;
|
||||||
|
width: $unit-10x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogClose {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $grey-50;
|
||||||
|
float: right;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTitle {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: $font-xlarge;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: $font-xlarge;
|
||||||
|
font-weight: $medium;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTop {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
|
.SubTitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-small;
|
||||||
|
font-weight: $medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogDescription {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogFooter {
|
||||||
|
align-items: flex-end;
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: ($unit * 1.5) ($unit * $multiplier) $unit-3x;
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Conflict {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
components/DialogContent/index.tsx
Normal file
43
components/DialogContent/index.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react'
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
import Overlay from '~components/Overlay'
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
extends React.DetailedHTMLProps<
|
||||||
|
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> {
|
||||||
|
onEscapeKeyDown: (event: KeyboardEvent) => void
|
||||||
|
onOpenAutoFocus: (event: Event) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<HTMLDivElement, Props>(function dialog(
|
||||||
|
{ children, ...props },
|
||||||
|
forwardedRef
|
||||||
|
) {
|
||||||
|
const classes = classNames(props.className, {
|
||||||
|
DialogContent: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<dialog className="Dialog">
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
{...props}
|
||||||
|
className={classes}
|
||||||
|
onOpenAutoFocus={props.onOpenAutoFocus}
|
||||||
|
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||||
|
ref={forwardedRef}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</dialog>
|
||||||
|
<Overlay visible={true} open={true} />
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default DialogContent
|
||||||
17
components/ExtendedMasterySelect/index.scss
Normal file
17
components/ExtendedMasterySelect/index.scss
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
.SelectSet {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.SelectTrigger.Left {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectTrigger.Right {
|
||||||
|
flex-grow: 0;
|
||||||
|
text-align: right;
|
||||||
|
min-width: 12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
163
components/ExtendedMasterySelect/index.tsx
Normal file
163
components/ExtendedMasterySelect/index.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Core dependencies
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
// UI Dependencies
|
||||||
|
import Select from '~components/Select'
|
||||||
|
import SelectItem from '~components/SelectItem'
|
||||||
|
|
||||||
|
// Styles and icons
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
interface Props {
|
||||||
|
name: string
|
||||||
|
object: 'ring'
|
||||||
|
dataSet: ItemSkill[]
|
||||||
|
leftSelectValue: number
|
||||||
|
leftSelectDisabled: boolean
|
||||||
|
rightSelectValue: number
|
||||||
|
sendValues: (left: number, right: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
selectDisabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExtendedMasterySelect = ({
|
||||||
|
name,
|
||||||
|
object,
|
||||||
|
dataSet,
|
||||||
|
leftSelectDisabled,
|
||||||
|
leftSelectValue,
|
||||||
|
rightSelectValue,
|
||||||
|
sendValues,
|
||||||
|
}: Props) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
const [leftSelectOpen, setLeftSelectOpen] = useState(false)
|
||||||
|
const [rightSelectOpen, setRightSelectOpen] = useState(false)
|
||||||
|
|
||||||
|
// Field properties
|
||||||
|
// prettier-ignore
|
||||||
|
const [currentItemSkill, setCurrentItemSkill] = useState<ItemSkill | undefined>(undefined)
|
||||||
|
const [currentItemValue, setCurrentItemValue] = useState(rightSelectValue)
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
// if (currentItemSkill) sendValues(currentItemSkill.id, currentItemValue)
|
||||||
|
|
||||||
|
// Set default values from props
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentItemSkill(dataSet.find((sk) => sk.id === leftSelectValue))
|
||||||
|
setCurrentItemValue(rightSelectValue)
|
||||||
|
}, [leftSelectValue, rightSelectValue])
|
||||||
|
|
||||||
|
// Methods: UI state management
|
||||||
|
function changeOpen(side: 'left' | 'right') {
|
||||||
|
if (side === 'left' && !leftSelectDisabled) {
|
||||||
|
setLeftSelectOpen(!leftSelectOpen)
|
||||||
|
} else if (side === 'right') {
|
||||||
|
setRightSelectOpen(!rightSelectOpen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
setLeftSelectOpen(false)
|
||||||
|
setRightSelectOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Rendering
|
||||||
|
function generateLeftOptions() {
|
||||||
|
let options: React.ReactNode[] = dataSet.map((skill, i) => {
|
||||||
|
return (
|
||||||
|
<SelectItem key={`${name}-key-${i}`} value={skill.id}>
|
||||||
|
{skill.name[locale]}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRightOptions() {
|
||||||
|
if (currentItemSkill && currentItemSkill.values) {
|
||||||
|
let options = currentItemSkill.values.map((value, i) => {
|
||||||
|
return (
|
||||||
|
<SelectItem key={`${name}-values-${i + 1}`} value={value}>
|
||||||
|
{value}
|
||||||
|
{currentItemSkill.suffix ? currentItemSkill.suffix : ''}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
options.unshift(
|
||||||
|
<SelectItem key={`${name}-values-0`} value="no-value">
|
||||||
|
{t('no_value')}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: User input detection
|
||||||
|
function handleLeftSelectChange(rawValue: string) {
|
||||||
|
const value = parseInt(rawValue)
|
||||||
|
const skill = dataSet.find((sk) => sk.id === value)
|
||||||
|
|
||||||
|
setCurrentItemSkill(skill)
|
||||||
|
setCurrentItemValue(0)
|
||||||
|
|
||||||
|
if (skill) sendValues(skill.id, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRightSelectChange(rawValue: string) {
|
||||||
|
const value = parseFloat(rawValue)
|
||||||
|
setCurrentItemValue(value)
|
||||||
|
|
||||||
|
if (currentItemSkill) sendValues(currentItemSkill.id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SelectSet">
|
||||||
|
<Select
|
||||||
|
key={`${name}_type`}
|
||||||
|
value={`${currentItemSkill ? currentItemSkill.id : 0}`}
|
||||||
|
open={leftSelectOpen}
|
||||||
|
disabled={leftSelectDisabled}
|
||||||
|
onValueChange={handleLeftSelectChange}
|
||||||
|
onOpenChange={() => changeOpen('left')}
|
||||||
|
onClose={onClose}
|
||||||
|
triggerClass="Left modal"
|
||||||
|
>
|
||||||
|
{generateLeftOptions()}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
key={`${name}_value`}
|
||||||
|
value={`${currentItemValue > 0 ? currentItemValue : 'no-value'}`}
|
||||||
|
open={rightSelectOpen}
|
||||||
|
onValueChange={handleRightSelectChange}
|
||||||
|
onOpenChange={() => changeOpen('right')}
|
||||||
|
onClose={onClose}
|
||||||
|
triggerClass={classNames({
|
||||||
|
Right: true,
|
||||||
|
modal: true,
|
||||||
|
hidden: currentItemSkill?.id === 0,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{generateRightOptions()}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtendedMasterySelect.defaultProps = defaultProps
|
||||||
|
|
||||||
|
export default ExtendedMasterySelect
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
background-color: var(--input-bg);
|
background-color: var(--input-bg);
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
border-radius: 6px;
|
border-radius: $input-corner;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: block;
|
display: block;
|
||||||
padding: $unit-2x;
|
padding: calc($unit-2x - 2px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&[type='number']::-webkit-inner-spin-button {
|
&[type='number']::-webkit-inner-spin-button {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import SelectItem from '~components/SelectItem'
|
||||||
import SelectGroup from '~components/SelectGroup'
|
import SelectGroup from '~components/SelectGroup'
|
||||||
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
import { jobGroups } from '~utils/jobGroups'
|
import { jobGroups } from '~data/jobGroups'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ const JobSection = (props: Props) => {
|
||||||
ref={selectRef}
|
ref={selectRef}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h3>{party.job?.name[locale]}</h3>
|
<h3>{party.job ? party.job.name[locale] : t('no_job')}</h3>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="JobSkills">
|
<ul className="JobSkills">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { SkillGroup, skillClassification } from '~utils/skillGroups'
|
import { SkillGroup, skillClassification } from '~data/skillGroups'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
.Login.Dialog form {
|
.Login.DialogContent {
|
||||||
|
gap: $unit;
|
||||||
|
min-width: $unit * 52;
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
padding: $unit-4x $unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit-3x;
|
||||||
|
padding: 0 $unit-3x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,8 @@ import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Input from '~components/LabelledInput'
|
import Input from '~components/LabelledInput'
|
||||||
import {
|
import { Dialog, DialogTrigger, DialogClose } from '~components/Dialog'
|
||||||
Dialog,
|
import DialogContent from '~components/DialogContent'
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
|
||||||
DialogClose,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
|
|
||||||
import changeLanguage from '~utils/changeLanguage'
|
import changeLanguage from '~utils/changeLanguage'
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
|
|
@ -203,7 +198,7 @@ const LoginModal = () => {
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Login Dialog"
|
className="Login"
|
||||||
onEscapeKeyDown={onEscapeKeyDown}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
onOpenAutoFocus={onOpenAutoFocus}
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -632,9 +632,7 @@ const PartyDetails = (props: Props) => {
|
||||||
<section className="DetailsWrapper">
|
<section className="DetailsWrapper">
|
||||||
<div className="PartyInfo">
|
<div className="PartyInfo">
|
||||||
<div className="Left">
|
<div className="Left">
|
||||||
<h1 className={name === '' ? 'empty' : ''}>
|
<h1 className={name ? '' : 'empty'}>{name ? name : t('no_title')}</h1>
|
||||||
{name !== '' ? name : 'Untitled'}
|
|
||||||
</h1>
|
|
||||||
<div className="attribution">
|
<div className="attribution">
|
||||||
{renderUserBlock()}
|
{renderUserBlock()}
|
||||||
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import SelectGroup from '~components/SelectGroup'
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
import { raidGroups } from '~utils/raidGroups'
|
import { raidGroups } from '~data/raidGroups'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
|
||||||
5
components/RingSelect/index.scss
Normal file
5
components/RingSelect/index.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.Rings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
150
components/RingSelect/index.tsx
Normal file
150
components/RingSelect/index.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Core dependencies
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
// UI dependencies
|
||||||
|
import ExtendedMasterySelect from '~components/ExtendedMasterySelect'
|
||||||
|
|
||||||
|
// Data
|
||||||
|
import { overMastery } from '~data/overMastery'
|
||||||
|
|
||||||
|
// Styles and icons
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { CharacterOverMastery, ExtendedMastery } from '~types'
|
||||||
|
|
||||||
|
const emptyRing: ExtendedMastery = {
|
||||||
|
modifier: 0,
|
||||||
|
strength: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
gridCharacter: GridCharacter
|
||||||
|
sendValues: (overMastery: CharacterOverMastery) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
||||||
|
// Ring value states
|
||||||
|
const [rings, setRings] = useState<CharacterOverMastery>({
|
||||||
|
1: { ...emptyRing, modifier: 1 },
|
||||||
|
2: { ...emptyRing, modifier: 2 },
|
||||||
|
3: emptyRing,
|
||||||
|
4: emptyRing,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (gridCharacter.over_mastery) {
|
||||||
|
setRings({
|
||||||
|
1: gridCharacter.over_mastery[0],
|
||||||
|
2: gridCharacter.over_mastery[1],
|
||||||
|
3: gridCharacter.over_mastery[2],
|
||||||
|
4: gridCharacter.over_mastery[3],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [gridCharacter])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sendValues(rings)
|
||||||
|
}, [rings])
|
||||||
|
|
||||||
|
function dataSet(index: number) {
|
||||||
|
const noValue = {
|
||||||
|
name: {
|
||||||
|
en: 'No over mastery bonus',
|
||||||
|
ja: 'EXリミットボーナスなし',
|
||||||
|
},
|
||||||
|
id: 0,
|
||||||
|
slug: 'no-bonus',
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 0,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
secondary: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 1:
|
||||||
|
return overMastery.a ? [overMastery.a[0]] : []
|
||||||
|
case 2:
|
||||||
|
return overMastery.a ? [overMastery.a[1]] : []
|
||||||
|
case 3:
|
||||||
|
return overMastery.b ? [noValue, ...overMastery.b] : []
|
||||||
|
case 4:
|
||||||
|
return overMastery.c ? [noValue, ...overMastery.c] : []
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveRingValues(index: number, left: number, right: number) {
|
||||||
|
console.log(`Receiving values from ${index}: ${left} ${right}`)
|
||||||
|
if (index == 1 || index == 2) {
|
||||||
|
setSyncedRingValues(index, right)
|
||||||
|
} else if (index == 3 && left == 0) {
|
||||||
|
setRings({
|
||||||
|
...rings,
|
||||||
|
3: {
|
||||||
|
modifier: 0,
|
||||||
|
strength: 0,
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
modifier: 0,
|
||||||
|
strength: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setRings({
|
||||||
|
...rings,
|
||||||
|
[index]: {
|
||||||
|
modifier: left,
|
||||||
|
strength: right,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSyncedRingValues(index: 1 | 2, value: number) {
|
||||||
|
console.log(`Setting synced value for ${index} with value ${value}`)
|
||||||
|
const atkValues = (dataSet(1)[0] as ItemSkill).values ?? []
|
||||||
|
const hpValues = (dataSet(2)[0] as ItemSkill).values ?? []
|
||||||
|
|
||||||
|
let found = index === 1 ? atkValues.indexOf(value) : hpValues.indexOf(value)
|
||||||
|
|
||||||
|
setRings({
|
||||||
|
...rings,
|
||||||
|
1: {
|
||||||
|
modifier: 1,
|
||||||
|
strength: atkValues[found],
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
modifier: 2,
|
||||||
|
strength: hpValues[found],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Rings">
|
||||||
|
{[...Array(4)].map((e, i) => {
|
||||||
|
const ringIndex = i + 1
|
||||||
|
const ringStat = rings[ringIndex]
|
||||||
|
return (
|
||||||
|
<ExtendedMasterySelect
|
||||||
|
name={`ring-${ringIndex}`}
|
||||||
|
object="ring"
|
||||||
|
key={`ring-${ringIndex}`}
|
||||||
|
dataSet={dataSet(ringIndex)}
|
||||||
|
leftSelectDisabled={i === 0 || i === 1}
|
||||||
|
leftSelectValue={ringStat.modifier ? ringStat.modifier : 0}
|
||||||
|
rightSelectValue={ringStat.strength ? ringStat.strength : 0}
|
||||||
|
sendValues={(left: number, right: number) => {
|
||||||
|
receiveRingValues(ringIndex, left, right)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RingSelect
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.Search.Dialog {
|
.Search.DialogContent {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Search.Dialog #NoResults {
|
.Search.DialogContent #NoResults {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Search.Dialog #NoResults h2 {
|
.Search.DialogContent #NoResults h2 {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: $font-large;
|
font-size: $font-large;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,12 @@ import { getCookie, setCookie } from 'cookies-next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
|
|
||||||
import {
|
import { Dialog, DialogTrigger, DialogClose } from '~components/Dialog'
|
||||||
Dialog,
|
import DialogContent from '~components/DialogContent'
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
|
||||||
DialogClose,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
|
|
||||||
import Input from '~components/LabelledInput'
|
import Input from '~components/LabelledInput'
|
||||||
import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar'
|
import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar'
|
||||||
import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar'
|
import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar'
|
||||||
|
|
@ -24,19 +20,18 @@ import WeaponResult from '~components/WeaponResult'
|
||||||
import SummonResult from '~components/SummonResult'
|
import SummonResult from '~components/SummonResult'
|
||||||
import JobSkillResult from '~components/JobSkillResult'
|
import JobSkillResult from '~components/JobSkillResult'
|
||||||
|
|
||||||
|
import type { DialogProps } from '@radix-ui/react-dialog'
|
||||||
import type { SearchableObject, SearchableObjectArray } from '~types'
|
import type { SearchableObject, SearchableObjectArray } from '~types'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props extends DialogProps {
|
||||||
send: (object: SearchableObject, position: number) => any
|
send: (object: SearchableObject, position: number) => any
|
||||||
placeholderText: string
|
placeholderText: string
|
||||||
fromPosition: number
|
fromPosition: number
|
||||||
job?: Job
|
job?: Job
|
||||||
object: 'weapons' | 'characters' | 'summons' | 'job_skills'
|
object: 'weapons' | 'characters' | 'summons' | 'job_skills'
|
||||||
children: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchModal = (props: Props) => {
|
const SearchModal = (props: Props) => {
|
||||||
|
|
@ -65,6 +60,10 @@ const SearchModal = (props: Props) => {
|
||||||
if (searchInput.current) searchInput.current.focus()
|
if (searchInput.current) searchInput.current.focus()
|
||||||
}, [searchInput])
|
}, [searchInput])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.open !== undefined) setOpen(props.open)
|
||||||
|
})
|
||||||
|
|
||||||
function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const text = event.target.value
|
const text = event.target.value
|
||||||
if (text.length) {
|
if (text.length) {
|
||||||
|
|
@ -335,8 +334,10 @@ const SearchModal = (props: Props) => {
|
||||||
setRecordCount(0)
|
setRecordCount(0)
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
if (props.onOpenChange) props.onOpenChange(false)
|
||||||
} else {
|
} else {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
|
if (props.onOpenChange) props.onOpenChange(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -354,7 +355,7 @@ const SearchModal = (props: Props) => {
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Search Dialog"
|
className="Search"
|
||||||
onEscapeKeyDown={onEscapeKeyDown}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
onOpenAutoFocus={onOpenAutoFocus}
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--input-bg-hover);
|
background-color: var(--input-bg-hover);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
|
@ -24,6 +28,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Disabled:hover {
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-placeholder] > span:not(.SelectIcon) {
|
&[data-placeholder] > span:not(.SelectIcon) {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [value, setValue] = useState('')
|
const [value, setValue] = useState('')
|
||||||
|
|
||||||
|
const triggerClasses = classNames(
|
||||||
|
{
|
||||||
|
SelectTrigger: true,
|
||||||
|
Disabled: props.disabled,
|
||||||
|
},
|
||||||
|
props.triggerClass
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpen(props.open)
|
setOpen(props.open)
|
||||||
}, [props.open])
|
}, [props.open])
|
||||||
|
|
@ -67,14 +75,18 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
||||||
onOpenChange={props.onOpenChange}
|
onOpenChange={props.onOpenChange}
|
||||||
>
|
>
|
||||||
<RadixSelect.Trigger
|
<RadixSelect.Trigger
|
||||||
className={classNames('SelectTrigger', props.triggerClass)}
|
className={triggerClasses}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
>
|
>
|
||||||
<RadixSelect.Value placeholder={props.placeholder} />
|
<RadixSelect.Value placeholder={props.placeholder} />
|
||||||
|
{!props.disabled ? (
|
||||||
<RadixSelect.Icon className="SelectIcon">
|
<RadixSelect.Icon className="SelectIcon">
|
||||||
<ArrowIcon />
|
<ArrowIcon />
|
||||||
</RadixSelect.Icon>
|
</RadixSelect.Icon>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</RadixSelect.Trigger>
|
</RadixSelect.Trigger>
|
||||||
|
|
||||||
<RadixSelect.Portal className="Select">
|
<RadixSelect.Portal className="Select">
|
||||||
|
|
|
||||||
29
components/SelectWithInput/index.scss
Normal file
29
components/SelectWithInput/index.scss
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
.SelectWithItem {
|
||||||
|
.InputSet {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.SelectTrigger {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
flex-grow: 0;
|
||||||
|
text-align: right;
|
||||||
|
width: 13rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.errors {
|
||||||
|
color: $error;
|
||||||
|
display: none;
|
||||||
|
padding: $unit 0;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
components/SelectWithInput/index.tsx
Normal file
200
components/SelectWithInput/index.tsx
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Core dependencies
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
// UI Dependencies
|
||||||
|
import Input from '~components/Input'
|
||||||
|
import Select from '~components/Select'
|
||||||
|
import SelectItem from '~components/SelectItem'
|
||||||
|
|
||||||
|
// Styles and icons
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
interface Props {
|
||||||
|
object: 'ax' | 'weapon_awakening' | 'character_awakening' | 'ring' | 'earring'
|
||||||
|
dataSet: ItemSkill[]
|
||||||
|
selectValue: number
|
||||||
|
selectDisabled: boolean
|
||||||
|
inputValue: number
|
||||||
|
awakeningLevel?: number
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
sendValidity: (isValid: boolean) => void
|
||||||
|
sendValues: (type: number, level: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
selectDisabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectWithInput = ({
|
||||||
|
object,
|
||||||
|
dataSet,
|
||||||
|
selectDisabled,
|
||||||
|
selectValue,
|
||||||
|
inputValue,
|
||||||
|
onOpenChange,
|
||||||
|
sendValidity,
|
||||||
|
sendValues,
|
||||||
|
}: Props) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
// Field properties
|
||||||
|
// prettier-ignore
|
||||||
|
const [currentItemSkill, setCurrentItemSkill] = useState<ItemSkill | undefined>(undefined)
|
||||||
|
const [fieldInputValue, setFieldInputValue] = useState(inputValue)
|
||||||
|
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const input = React.createRef<HTMLInputElement>()
|
||||||
|
|
||||||
|
// Classes
|
||||||
|
const inputClasses = classNames({
|
||||||
|
Bound: true,
|
||||||
|
Hidden: currentItemSkill?.id === 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorClasses = classNames({
|
||||||
|
errors: true,
|
||||||
|
visible: error !== '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
|
||||||
|
// Set default values from props
|
||||||
|
useEffect(() => {
|
||||||
|
const found = dataSet.find((sk) => sk.id === selectValue)
|
||||||
|
if (found) {
|
||||||
|
setCurrentItemSkill(found)
|
||||||
|
setFieldInputValue(inputValue)
|
||||||
|
}
|
||||||
|
}, [selectValue, inputValue])
|
||||||
|
|
||||||
|
// Methods: UI state management
|
||||||
|
function changeOpen() {
|
||||||
|
if (!selectDisabled) {
|
||||||
|
setOpen(!open)
|
||||||
|
if (onOpenChange) onOpenChange(!open)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
if (onOpenChange) onOpenChange(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Rendering
|
||||||
|
function generateOptions() {
|
||||||
|
let options: React.ReactNode[] = dataSet.map((skill, i) => {
|
||||||
|
return (
|
||||||
|
<SelectItem key={i} value={skill.id}>
|
||||||
|
{skill.name[locale]}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: User input detection
|
||||||
|
function handleSelectChange(rawValue: string) {
|
||||||
|
const value = parseInt(rawValue)
|
||||||
|
const skill = dataSet.find((sk) => sk.id === value)
|
||||||
|
|
||||||
|
if (skill) {
|
||||||
|
setCurrentItemSkill(skill)
|
||||||
|
sendValues(skill.id, fieldInputValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
const value = parseFloat(event.target.value)
|
||||||
|
if (handleInputError(value)) setFieldInputValue(value)
|
||||||
|
|
||||||
|
if (currentItemSkill) sendValues(currentItemSkill.id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Handle error
|
||||||
|
function handleInputError(value: number) {
|
||||||
|
let error = ''
|
||||||
|
|
||||||
|
if (currentItemSkill) {
|
||||||
|
if (value < currentItemSkill.minValue) {
|
||||||
|
error = t(`${object}.errors.value_too_low`, {
|
||||||
|
minValue: currentItemSkill.minValue,
|
||||||
|
})
|
||||||
|
} else if (value > currentItemSkill.maxValue) {
|
||||||
|
error = t(`${object}.errors.value_too_high`, {
|
||||||
|
maxValue: currentItemSkill.maxValue,
|
||||||
|
})
|
||||||
|
} else if (!currentItemSkill.fractional && value % 1 != 0) {
|
||||||
|
error = t(`${object}.errors.value_not_whole`)
|
||||||
|
} else if (!value || value <= 0) {
|
||||||
|
error = t(`${object}.errors.value_empty`)
|
||||||
|
} else {
|
||||||
|
error = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(error)
|
||||||
|
|
||||||
|
return error.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeString = () => {
|
||||||
|
let placeholder = ''
|
||||||
|
|
||||||
|
if (currentItemSkill) {
|
||||||
|
const minValue = currentItemSkill.minValue
|
||||||
|
const maxValue = currentItemSkill.maxValue
|
||||||
|
placeholder = `${minValue}~${maxValue}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SelectWithItem">
|
||||||
|
<div className="InputSet">
|
||||||
|
<Select
|
||||||
|
key={`${currentItemSkill?.name.en}_type`}
|
||||||
|
value={`${currentItemSkill ? currentItemSkill.id : 0}`}
|
||||||
|
open={open}
|
||||||
|
disabled={selectDisabled}
|
||||||
|
onValueChange={handleSelectChange}
|
||||||
|
onOpenChange={changeOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
triggerClass="modal"
|
||||||
|
>
|
||||||
|
{generateOptions()}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
value={fieldInputValue}
|
||||||
|
className={inputClasses}
|
||||||
|
type="number"
|
||||||
|
placeholder={rangeString()}
|
||||||
|
min={currentItemSkill?.minValue}
|
||||||
|
max={currentItemSkill?.maxValue}
|
||||||
|
step="1"
|
||||||
|
onChange={handleInputChange}
|
||||||
|
visible={currentItemSkill ? 'true' : 'false'}
|
||||||
|
ref={input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className={errorClasses}>{error}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectWithInput.defaultProps = defaultProps
|
||||||
|
|
||||||
|
export default SelectWithInput
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
.Signup.Dialog form {
|
.Signup.DialogContent {
|
||||||
|
gap: $unit;
|
||||||
|
min-width: $unit * 52;
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
padding: $unit-4x $unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit-2x;
|
||||||
|
padding: 0 $unit-3x;
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
color: $grey-50;
|
color: $grey-50;
|
||||||
|
|
@ -20,3 +29,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,8 @@ import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Input from '~components/LabelledInput'
|
import Input from '~components/LabelledInput'
|
||||||
import {
|
import { Dialog, DialogTrigger, DialogClose } from '~components/Dialog'
|
||||||
Dialog,
|
import DialogContent from '~components/DialogContent'
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
|
||||||
DialogClose,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
@ -283,7 +278,7 @@ const SignupModal = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Signup Dialog"
|
className="Signup"
|
||||||
onEscapeKeyDown={onEscapeKeyDown}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
onOpenAutoFocus={onOpenAutoFocus}
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.18s ease-in-out;
|
transition: $duration-zoom all ease-in-out;
|
||||||
|
|
||||||
&:hover .icon svg {
|
&:hover .icon svg {
|
||||||
fill: var(--icon-secondary-hover);
|
fill: var(--icon-secondary-hover);
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
transition: $duration-color-fade fill ease-in-out;
|
||||||
fill: var(--icon-secondary);
|
fill: var(--icon-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { Dialog, DialogContent } from '~components/Dialog'
|
import { Dialog } from '~components/Dialog'
|
||||||
|
import DialogContent from '~components/DialogContent'
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
import Overlay from '~components/Overlay'
|
import Overlay from '~components/Overlay'
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ const WeaponConflictModal = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Conflict Dialog"
|
className="Conflict"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
onEscapeKeyDown={close}
|
onEscapeKeyDown={close}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,18 @@ import { useTranslation } from 'next-i18next'
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
|
import Alert from '~components/Alert'
|
||||||
import WeaponUnit from '~components/WeaponUnit'
|
import WeaponUnit from '~components/WeaponUnit'
|
||||||
import ExtraWeapons from '~components/ExtraWeapons'
|
import ExtraWeapons from '~components/ExtraWeapons'
|
||||||
|
import WeaponConflictModal from '~components/WeaponConflictModal'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
import { accountState } from '~utils/accountState'
|
||||||
|
|
||||||
import type { DetailsObject, SearchableObject } from '~types'
|
import type { DetailsObject, SearchableObject } from '~types'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import WeaponConflictModal from '~components/WeaponConflictModal'
|
|
||||||
import Alert from '~components/Alert'
|
|
||||||
import { accountState } from '~utils/accountState'
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -198,6 +198,21 @@ const WeaponGrid = (props: Props) => {
|
||||||
setIncoming(undefined)
|
setIncoming(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeWeapon(id: string) {
|
||||||
|
try {
|
||||||
|
const response = await api.endpoints.grid_weapons.destroy({ id: id })
|
||||||
|
const data = response.data
|
||||||
|
|
||||||
|
if (data.position === -1) {
|
||||||
|
appState.grid.weapons.mainWeapon = undefined
|
||||||
|
} else {
|
||||||
|
appState.grid.weapons.allWeapons[response.data.position] = undefined
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Updating uncap level
|
// Methods: Updating uncap level
|
||||||
// Note: Saves, but debouncing is not working properly
|
// Note: Saves, but debouncing is not working properly
|
||||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||||
|
|
@ -254,7 +269,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||||
console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
|
// console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
|
||||||
if (appState.grid.weapons.mainWeapon && position == -1)
|
if (appState.grid.weapons.mainWeapon && position == -1)
|
||||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
||||||
else {
|
else {
|
||||||
|
|
@ -292,6 +307,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
key="grid_mainhand"
|
key="grid_mainhand"
|
||||||
position={-1}
|
position={-1}
|
||||||
unitType={0}
|
unitType={0}
|
||||||
|
removeWeapon={removeWeapon}
|
||||||
updateObject={receiveWeaponFromSearch}
|
updateObject={receiveWeaponFromSearch}
|
||||||
updateUncap={initiateUncapUpdate}
|
updateUncap={initiateUncapUpdate}
|
||||||
/>
|
/>
|
||||||
|
|
@ -305,6 +321,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
position={i}
|
position={i}
|
||||||
unitType={1}
|
unitType={1}
|
||||||
|
removeWeapon={removeWeapon}
|
||||||
updateObject={receiveWeaponFromSearch}
|
updateObject={receiveWeaponFromSearch}
|
||||||
updateUncap={initiateUncapUpdate}
|
updateUncap={initiateUncapUpdate}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import * as HoverCard from '@radix-ui/react-hover-card'
|
||||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
|
|
||||||
import { axData } from '~utils/axData'
|
import ax from '~data/ax'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ const WeaponHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createPrimaryAxSkillString = () => {
|
const createPrimaryAxSkillString = () => {
|
||||||
const primaryAxSkills = axData[props.gridWeapon.object.ax_type - 1]
|
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1]
|
||||||
|
|
||||||
if (props.gridWeapon.ax) {
|
if (props.gridWeapon.ax) {
|
||||||
const simpleAxSkill = props.gridWeapon.ax[0]
|
const simpleAxSkill = props.gridWeapon.ax[0]
|
||||||
|
|
@ -97,7 +97,7 @@ const WeaponHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createSecondaryAxSkillString = () => {
|
const createSecondaryAxSkillString = () => {
|
||||||
const primaryAxSkills = axData[props.gridWeapon.object.ax_type - 1]
|
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1]
|
||||||
|
|
||||||
if (props.gridWeapon.ax) {
|
if (props.gridWeapon.ax) {
|
||||||
const primarySimpleAxSkill = props.gridWeapon.ax[0]
|
const primarySimpleAxSkill = props.gridWeapon.ax[0]
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,41 @@
|
||||||
.Weapon.Dialog {
|
.Weapon.DialogContent {
|
||||||
|
gap: $unit;
|
||||||
min-width: 480px;
|
min-width: 480px;
|
||||||
|
|
||||||
@include breakpoint(phone) {
|
@include breakpoint(phone) {
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.DialogHeader {
|
||||||
|
transition: 0.18s padding-top ease-in-out;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&.Scrolled {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.34);
|
||||||
|
padding-top: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
transition: 0.2s width ease-in-out;
|
||||||
|
width: $unit-6x !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DialogTitle {
|
||||||
|
font-size: $font-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SubTitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mods {
|
.mods {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $unit * 4;
|
gap: $unit * 4;
|
||||||
|
padding: 0 $unit-4x;
|
||||||
|
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||||
import { getCookie } from 'cookies-next'
|
import { getCookie } from 'cookies-next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
@ -7,11 +7,10 @@ import { AxiosResponse } from 'axios'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from '~components/Dialog'
|
} from '~components/Dialog'
|
||||||
|
import DialogContent from '~components/DialogContent'
|
||||||
import AXSelect from '~components/AxSelect'
|
import AXSelect from '~components/AxSelect'
|
||||||
import AwakeningSelect from '~components/AwakeningSelect'
|
import AwakeningSelect from '~components/AwakeningSelect'
|
||||||
import ElementToggle from '~components/ElementToggle'
|
import ElementToggle from '~components/ElementToggle'
|
||||||
|
|
@ -41,10 +40,16 @@ interface GridWeaponObject {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridWeapon: GridWeapon
|
gridWeapon: GridWeapon
|
||||||
children: React.ReactNode
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponModal = (props: Props) => {
|
const WeaponModal = ({
|
||||||
|
gridWeapon,
|
||||||
|
open: modalOpen,
|
||||||
|
children,
|
||||||
|
onOpenChange,
|
||||||
|
}: PropsWithChildren<Props>) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
@ -65,7 +70,7 @@ const WeaponModal = (props: Props) => {
|
||||||
|
|
||||||
const [element, setElement] = useState(-1)
|
const [element, setElement] = useState(-1)
|
||||||
|
|
||||||
const [awakeningType, setAwakeningType] = useState(-1)
|
const [awakeningType, setAwakeningType] = useState(0)
|
||||||
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
||||||
|
|
||||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||||
|
|
@ -89,10 +94,14 @@ const WeaponModal = (props: Props) => {
|
||||||
const [awakeningOpen, setAwakeningOpen] = useState(false)
|
const [awakeningOpen, setAwakeningOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setElement(props.gridWeapon.element)
|
setOpen(modalOpen)
|
||||||
|
}, [modalOpen])
|
||||||
|
|
||||||
if (props.gridWeapon.weapon_keys) {
|
useEffect(() => {
|
||||||
props.gridWeapon.weapon_keys.forEach((key) => {
|
setElement(gridWeapon.element)
|
||||||
|
|
||||||
|
if (gridWeapon.weapon_keys) {
|
||||||
|
gridWeapon.weapon_keys.forEach((key) => {
|
||||||
if (key.slot + 1 === 1) {
|
if (key.slot + 1 === 1) {
|
||||||
setWeaponKey1(key)
|
setWeaponKey1(key)
|
||||||
} else if (key.slot + 1 === 2) {
|
} else if (key.slot + 1 === 2) {
|
||||||
|
|
@ -102,7 +111,7 @@ const WeaponModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [props])
|
}, [gridWeapon])
|
||||||
|
|
||||||
function receiveAxValues(
|
function receiveAxValues(
|
||||||
primaryAxModifier: number,
|
primaryAxModifier: number,
|
||||||
|
|
@ -133,29 +142,26 @@ const WeaponModal = (props: Props) => {
|
||||||
function prepareObject() {
|
function prepareObject() {
|
||||||
let object: GridWeaponObject = { weapon: {} }
|
let object: GridWeaponObject = { weapon: {} }
|
||||||
|
|
||||||
if (props.gridWeapon.object.element == 0) object.weapon.element = element
|
if (gridWeapon.object.element == 0) object.weapon.element = element
|
||||||
|
|
||||||
if (
|
if ([2, 3, 17, 24].includes(gridWeapon.object.series) && weaponKey1Id) {
|
||||||
[2, 3, 17, 24].includes(props.gridWeapon.object.series) &&
|
|
||||||
weaponKey1Id
|
|
||||||
) {
|
|
||||||
object.weapon.weapon_key1_id = weaponKey1Id
|
object.weapon.weapon_key1_id = weaponKey1Id
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([2, 3, 17].includes(props.gridWeapon.object.series) && weaponKey2Id)
|
if ([2, 3, 17].includes(gridWeapon.object.series) && weaponKey2Id)
|
||||||
object.weapon.weapon_key2_id = weaponKey2Id
|
object.weapon.weapon_key2_id = weaponKey2Id
|
||||||
|
|
||||||
if (props.gridWeapon.object.series == 17 && weaponKey3Id)
|
if (gridWeapon.object.series == 17 && weaponKey3Id)
|
||||||
object.weapon.weapon_key3_id = weaponKey3Id
|
object.weapon.weapon_key3_id = weaponKey3Id
|
||||||
|
|
||||||
if (props.gridWeapon.object.ax && props.gridWeapon.object.ax_type > 0) {
|
if (gridWeapon.object.ax && gridWeapon.object.ax_type > 0) {
|
||||||
object.weapon.ax_modifier1 = primaryAxModifier
|
object.weapon.ax_modifier1 = primaryAxModifier
|
||||||
object.weapon.ax_modifier2 = secondaryAxModifier
|
object.weapon.ax_modifier2 = secondaryAxModifier
|
||||||
object.weapon.ax_strength1 = primaryAxValue
|
object.weapon.ax_strength1 = primaryAxValue
|
||||||
object.weapon.ax_strength2 = secondaryAxValue
|
object.weapon.ax_strength2 = secondaryAxValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.gridWeapon.object.awakening) {
|
if (gridWeapon.object.awakening) {
|
||||||
object.weapon.awakening_type = awakeningType
|
object.weapon.awakening_type = awakeningType
|
||||||
object.weapon.awakening_level = awakeningLevel
|
object.weapon.awakening_level = awakeningLevel
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +172,7 @@ const WeaponModal = (props: Props) => {
|
||||||
async function updateWeapon() {
|
async function updateWeapon() {
|
||||||
const updateObject = prepareObject()
|
const updateObject = prepareObject()
|
||||||
return await api.endpoints.grid_weapons
|
return await api.endpoints.grid_weapons
|
||||||
.update(props.gridWeapon.id, updateObject, headers)
|
.update(gridWeapon.id, updateObject, headers)
|
||||||
.then((response) => processResult(response))
|
.then((response) => processResult(response))
|
||||||
.catch((error) => processError(error))
|
.catch((error) => processError(error))
|
||||||
}
|
}
|
||||||
|
|
@ -222,11 +228,11 @@ const WeaponModal = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
||||||
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17, 22].includes(gridWeapon.object.series) ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
open={weaponKey1Open}
|
open={weaponKey1Open}
|
||||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={gridWeapon.object.series}
|
||||||
slot={0}
|
slot={0}
|
||||||
onOpenChange={() => openSelect(1)}
|
onOpenChange={() => openSelect(1)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
|
@ -236,11 +242,11 @@ const WeaponModal = (props: Props) => {
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17].includes(gridWeapon.object.series) ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
open={weaponKey2Open}
|
open={weaponKey2Open}
|
||||||
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
|
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={gridWeapon.object.series}
|
||||||
slot={1}
|
slot={1}
|
||||||
onOpenChange={() => openSelect(2)}
|
onOpenChange={() => openSelect(2)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
|
@ -250,11 +256,11 @@ const WeaponModal = (props: Props) => {
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.gridWeapon.object.series == 17 ? (
|
{gridWeapon.object.series == 17 ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
open={weaponKey3Open}
|
open={weaponKey3Open}
|
||||||
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
|
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={gridWeapon.object.series}
|
||||||
slot={2}
|
slot={2}
|
||||||
onOpenChange={() => openSelect(3)}
|
onOpenChange={() => openSelect(3)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
|
@ -264,12 +270,11 @@ const WeaponModal = (props: Props) => {
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.gridWeapon.object.series == 24 &&
|
{gridWeapon.object.series == 24 && gridWeapon.object.uncap.ulb ? (
|
||||||
props.gridWeapon.object.uncap.ulb ? (
|
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
open={weaponKey4Open}
|
open={weaponKey4Open}
|
||||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={gridWeapon.object.series}
|
||||||
slot={0}
|
slot={0}
|
||||||
onOpenChange={() => openSelect(4)}
|
onOpenChange={() => openSelect(4)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
|
@ -287,8 +292,8 @@ const WeaponModal = (props: Props) => {
|
||||||
<section>
|
<section>
|
||||||
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
|
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
|
||||||
<AXSelect
|
<AXSelect
|
||||||
axType={props.gridWeapon.object.ax_type}
|
axType={gridWeapon.object.ax_type}
|
||||||
currentSkills={props.gridWeapon.ax}
|
currentSkills={gridWeapon.ax}
|
||||||
onOpenChange={receiveAxOpen}
|
onOpenChange={receiveAxOpen}
|
||||||
sendValidity={receiveValidity}
|
sendValidity={receiveValidity}
|
||||||
sendValues={receiveAxValues}
|
sendValues={receiveAxValues}
|
||||||
|
|
@ -303,8 +308,8 @@ const WeaponModal = (props: Props) => {
|
||||||
<h3>{t('modals.weapon.subtitles.awakening')}</h3>
|
<h3>{t('modals.weapon.subtitles.awakening')}</h3>
|
||||||
<AwakeningSelect
|
<AwakeningSelect
|
||||||
object="weapon"
|
object="weapon"
|
||||||
awakeningType={props.gridWeapon.awakening?.type}
|
type={gridWeapon.awakening?.type}
|
||||||
awakeningLevel={props.gridWeapon.awakening?.level}
|
level={gridWeapon.awakening?.level}
|
||||||
onOpenChange={receiveAwakeningOpen}
|
onOpenChange={receiveAwakeningOpen}
|
||||||
sendValidity={receiveValidity}
|
sendValidity={receiveValidity}
|
||||||
sendValues={receiveAwakeningValues}
|
sendValues={receiveAwakeningValues}
|
||||||
|
|
@ -313,13 +318,14 @@ const WeaponModal = (props: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openChange(open: boolean) {
|
function handleOpenChange(open: boolean) {
|
||||||
if (props.gridWeapon.object.ax || props.gridWeapon.object.awakening) {
|
if (gridWeapon.object.ax || gridWeapon.object.awakening) {
|
||||||
setFormValid(false)
|
setFormValid(false)
|
||||||
} else {
|
} else {
|
||||||
setFormValid(true)
|
setFormValid(true)
|
||||||
}
|
}
|
||||||
setOpen(open)
|
setOpen(open)
|
||||||
|
onOpenChange(open)
|
||||||
}
|
}
|
||||||
|
|
||||||
const anySelectOpen =
|
const anySelectOpen =
|
||||||
|
|
@ -341,20 +347,25 @@ const WeaponModal = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// TODO: Refactor into Dialog component
|
// TODO: Refactor into Dialog component
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="Weapon Dialog"
|
className="Weapon"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
onEscapeKeyDown={onEscapeKeyDown}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
>
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
|
<img
|
||||||
|
alt={gridWeapon.object.name[locale]}
|
||||||
|
className="DialogImage"
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${gridWeapon.object.granblue_id}.jpg`}
|
||||||
|
/>
|
||||||
<div className="DialogTop">
|
<div className="DialogTop">
|
||||||
<DialogTitle className="SubTitle">
|
<DialogTitle className="SubTitle">
|
||||||
{t('modals.weapon.title')}
|
{t('modals.weapon.title')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogTitle className="DialogTitle">
|
<DialogTitle className="DialogTitle">
|
||||||
{props.gridWeapon.object.name[locale]}
|
{gridWeapon.object.name[locale]}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</div>
|
</div>
|
||||||
<DialogClose className="DialogClose" asChild>
|
<DialogClose className="DialogClose" asChild>
|
||||||
|
|
@ -365,12 +376,12 @@ const WeaponModal = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mods">
|
<div className="mods">
|
||||||
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
{gridWeapon.object.element == 0 ? elementSelect() : ''}
|
||||||
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
{[2, 3, 17, 24].includes(gridWeapon.object.series) ? keySelect() : ''}
|
||||||
? keySelect()
|
{gridWeapon.object.ax ? axSelect() : ''}
|
||||||
: ''}
|
{gridWeapon.awakening ? awakeningSelect() : ''}
|
||||||
{props.gridWeapon.object.ax ? axSelect() : ''}
|
</div>
|
||||||
{props.gridWeapon.awakening ? awakeningSelect() : ''}
|
<div className="DialogFooter">
|
||||||
<Button
|
<Button
|
||||||
contained={true}
|
contained={true}
|
||||||
onClick={updateWeapon}
|
onClick={updateWeapon}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
emptyWeaponSeriesState,
|
emptyWeaponSeriesState,
|
||||||
} from '~utils/emptyStates'
|
} from '~utils/emptyStates'
|
||||||
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
||||||
import { weaponSeries } from '~utils/weaponSeries'
|
import { weaponSeries } from '~data/weaponSeries'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,16 @@
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .Button {
|
.Button {
|
||||||
display: block;
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .Button,
|
||||||
|
.Button.Clicked {
|
||||||
|
pointer-events: initial;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.editable .WeaponImage:hover {
|
&.editable .WeaponImage:hover {
|
||||||
|
|
@ -95,15 +103,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
|
||||||
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.14);
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
left: $unit;
|
|
||||||
top: $unit;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: $font-button;
|
font-size: $font-button;
|
||||||
|
|
@ -123,7 +122,7 @@
|
||||||
margin-bottom: calc($unit / 4);
|
margin-bottom: calc($unit / 4);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.18s ease-in-out;
|
transition: $duration-zoom all ease-in-out;
|
||||||
|
|
||||||
&:hover .icon svg {
|
&:hover .icon svg {
|
||||||
fill: var(--icon-secondary-hover);
|
fill: var(--icon-secondary-hover);
|
||||||
|
|
@ -170,6 +169,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
transition: $duration-color-fade fill ease-in-out;
|
||||||
fill: var(--icon-secondary);
|
fill: var(--icon-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,25 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState, MouseEvent } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { Trans, useTranslation } from 'next-i18next'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import Alert from '~components/Alert'
|
||||||
import SearchModal from '~components/SearchModal'
|
import SearchModal from '~components/SearchModal'
|
||||||
import WeaponModal from '~components/WeaponModal'
|
import WeaponModal from '~components/WeaponModal'
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
ContextMenuContent,
|
||||||
|
} from '~components/ContextMenu'
|
||||||
|
import ContextMenuItem from '~components/ContextMenuItem'
|
||||||
import WeaponHovercard from '~components/WeaponHovercard'
|
import WeaponHovercard from '~components/WeaponHovercard'
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
import Button from '~components/Button'
|
import Button from '~components/Button'
|
||||||
|
|
||||||
import type { SearchableObject } from '~types'
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import { axData } from '~utils/axData'
|
import ax from '~data/ax'
|
||||||
import { weaponAwakening } from '~utils/awakening'
|
import { weaponAwakening } from '~data/awakening'
|
||||||
|
|
||||||
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'
|
||||||
|
|
@ -23,48 +30,147 @@ interface Props {
|
||||||
unitType: 0 | 1
|
unitType: 0 | 1
|
||||||
position: number
|
position: number
|
||||||
editable: boolean
|
editable: boolean
|
||||||
|
removeWeapon: (id: string) => void
|
||||||
updateObject: (object: SearchableObject, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponUnit = (props: Props) => {
|
const WeaponUnit = ({
|
||||||
|
gridWeapon,
|
||||||
|
unitType,
|
||||||
|
position,
|
||||||
|
editable,
|
||||||
|
removeWeapon: sendWeaponToRemove,
|
||||||
|
updateObject,
|
||||||
|
updateUncap,
|
||||||
|
}: Props) => {
|
||||||
|
// Translations and locale
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
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({
|
const classes = classNames({
|
||||||
WeaponUnit: true,
|
WeaponUnit: true,
|
||||||
mainhand: props.unitType == 0,
|
mainhand: unitType == 0,
|
||||||
grid: props.unitType == 1,
|
grid: unitType == 1,
|
||||||
editable: props.editable,
|
editable: editable,
|
||||||
filled: props.gridWeapon !== undefined,
|
filled: gridWeapon !== undefined,
|
||||||
empty: props.gridWeapon == undefined,
|
empty: gridWeapon == undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridWeapon = props.gridWeapon
|
const buttonClasses = classNames({
|
||||||
|
Options: true,
|
||||||
|
Clicked: contextMenuOpen,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Other
|
||||||
const weapon = gridWeapon?.object
|
const weapon = gridWeapon?.object
|
||||||
|
|
||||||
|
// Hooks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateImageUrl()
|
generateImageUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Methods: Convenience
|
||||||
|
function canBeModified(gridWeapon: GridWeapon) {
|
||||||
|
const weapon = gridWeapon.object
|
||||||
|
|
||||||
|
return (
|
||||||
|
weapon.ax ||
|
||||||
|
weapon.awakening ||
|
||||||
|
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Open layer
|
||||||
|
function openWeaponModal(event: Event) {
|
||||||
|
setDetailsModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ''
|
let imgSrc = ''
|
||||||
if (props.gridWeapon) {
|
if (gridWeapon) {
|
||||||
const weapon = props.gridWeapon.object!
|
const weapon = gridWeapon.object!
|
||||||
|
|
||||||
if (props.unitType == 0) {
|
if (unitType == 0) {
|
||||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
if (gridWeapon.object.element == 0 && gridWeapon.element)
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${gridWeapon.element}.jpg`
|
||||||
else
|
else
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
||||||
} else {
|
} else {
|
||||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
if (gridWeapon.object.element == 0 && gridWeapon.element)
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg`
|
||||||
else
|
else
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
||||||
}
|
}
|
||||||
|
|
@ -74,29 +180,30 @@ const WeaponUnit = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeholderImageUrl() {
|
function placeholderImageUrl() {
|
||||||
return props.unitType == 0
|
return unitType == 0
|
||||||
? '/images/placeholders/placeholder-weapon-main.png'
|
? '/images/placeholders/placeholder-weapon-main.png'
|
||||||
: '/images/placeholders/placeholder-weapon-grid.png'
|
: '/images/placeholders/placeholder-weapon-grid.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Methods: Image element rendering
|
||||||
function awakeningImage() {
|
function awakeningImage() {
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.awakening &&
|
gridWeapon.object.awakening &&
|
||||||
props.gridWeapon.awakening &&
|
gridWeapon.awakening &&
|
||||||
props.gridWeapon.awakening.type >= 0 &&
|
gridWeapon.awakening.type > 0 &&
|
||||||
props.gridWeapon.awakening.type != null
|
gridWeapon.awakening.type != null
|
||||||
) {
|
) {
|
||||||
const awakening = weaponAwakening.find(
|
const awakening = weaponAwakening.find(
|
||||||
(awakening) => awakening.id === props.gridWeapon?.awakening?.type
|
(awakening) => awakening.id === gridWeapon?.awakening?.type
|
||||||
)
|
)
|
||||||
const name = awakening?.name[locale]
|
const name = awakening?.name[locale]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt={`${name} Lv${props.gridWeapon.awakening.level}`}
|
alt={`${name} Lv${gridWeapon.awakening.level}`}
|
||||||
className="Awakening"
|
className="Awakening"
|
||||||
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/weapon_${props.gridWeapon.awakening.type}.png`}
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/weapon_${gridWeapon.awakening.type}.png`}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -109,18 +216,18 @@ const WeaponUnit = (props: Props) => {
|
||||||
|
|
||||||
// If there is a grid weapon, it is a Draconic Weapon and it has keys
|
// If there is a grid weapon, it is a Draconic Weapon and it has keys
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 3 &&
|
gridWeapon.object.series === 3 &&
|
||||||
props.gridWeapon.weapon_keys
|
gridWeapon.weapon_keys
|
||||||
) {
|
) {
|
||||||
if (index === 0 && props.gridWeapon.weapon_keys[0]) {
|
if (index === 0 && gridWeapon.weapon_keys[0]) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[0].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[0].name[locale]}`
|
||||||
filename = `${props.gridWeapon.weapon_keys[0].slug}.png`
|
filename = `${gridWeapon.weapon_keys[0].slug}.png`
|
||||||
} else if (index === 1 && props.gridWeapon.weapon_keys[1]) {
|
} else if (index === 1 && gridWeapon.weapon_keys[1]) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[1].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[1].name[locale]}`
|
||||||
|
|
||||||
const element = props.gridWeapon.object.element
|
const element = gridWeapon.object.element
|
||||||
filename = `${props.gridWeapon.weapon_keys[1].slug}-${element}.png`
|
filename = `${gridWeapon.weapon_keys[1].slug}-${element}.png`
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -137,12 +244,12 @@ const WeaponUnit = (props: Props) => {
|
||||||
function telumaImages() {
|
function telumaImages() {
|
||||||
let images: JSX.Element[] = []
|
let images: JSX.Element[] = []
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 3 &&
|
gridWeapon.object.series === 3 &&
|
||||||
props.gridWeapon.weapon_keys &&
|
gridWeapon.weapon_keys &&
|
||||||
props.gridWeapon.weapon_keys.length > 0
|
gridWeapon.weapon_keys.length > 0
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < props.gridWeapon.weapon_keys.length; i++) {
|
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) {
|
||||||
const image = telumaImage(i)
|
const image = telumaImage(i)
|
||||||
if (image) images.push(image)
|
if (image) images.push(image)
|
||||||
}
|
}
|
||||||
|
|
@ -158,27 +265,27 @@ const WeaponUnit = (props: Props) => {
|
||||||
|
|
||||||
// If there is a grid weapon, it is a Dark Opus Weapon and it has keys
|
// If there is a grid weapon, it is a Dark Opus Weapon and it has keys
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 17 &&
|
gridWeapon.object.series === 17 &&
|
||||||
props.gridWeapon.weapon_keys
|
gridWeapon.weapon_keys
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
props.gridWeapon.weapon_keys[index] &&
|
gridWeapon.weapon_keys[index] &&
|
||||||
(props.gridWeapon.weapon_keys[index].slot === 1 ||
|
(gridWeapon.weapon_keys[index].slot === 1 ||
|
||||||
props.gridWeapon.weapon_keys[index].slot === 2)
|
gridWeapon.weapon_keys[index].slot === 2)
|
||||||
) {
|
) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[index].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[index].name[locale]}`
|
||||||
filename = `${props.gridWeapon.weapon_keys[index].slug}.png`
|
filename = `${gridWeapon.weapon_keys[index].slug}.png`
|
||||||
} else if (
|
} else if (
|
||||||
props.gridWeapon.weapon_keys[index] &&
|
gridWeapon.weapon_keys[index] &&
|
||||||
props.gridWeapon.weapon_keys[index].slot === 0
|
gridWeapon.weapon_keys[index].slot === 0
|
||||||
) {
|
) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[index].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[index].name[locale]}`
|
||||||
|
|
||||||
const weapon = props.gridWeapon.object.proficiency
|
const weapon = gridWeapon.object.proficiency
|
||||||
|
|
||||||
const suffix = `${weapon}`
|
const suffix = `${weapon}`
|
||||||
filename = `${props.gridWeapon.weapon_keys[index].slug}-${suffix}.png`
|
filename = `${gridWeapon.weapon_keys[index].slug}-${suffix}.png`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,12 +302,12 @@ const WeaponUnit = (props: Props) => {
|
||||||
function ultimaImages() {
|
function ultimaImages() {
|
||||||
let images: JSX.Element[] = []
|
let images: JSX.Element[] = []
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 17 &&
|
gridWeapon.object.series === 17 &&
|
||||||
props.gridWeapon.weapon_keys &&
|
gridWeapon.weapon_keys &&
|
||||||
props.gridWeapon.weapon_keys.length > 0
|
gridWeapon.weapon_keys.length > 0
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < props.gridWeapon.weapon_keys.length; i++) {
|
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) {
|
||||||
const image = ultimaImage(i)
|
const image = ultimaImage(i)
|
||||||
if (image) images.push(image)
|
if (image) images.push(image)
|
||||||
}
|
}
|
||||||
|
|
@ -216,29 +323,29 @@ const WeaponUnit = (props: Props) => {
|
||||||
|
|
||||||
// If there is a grid weapon, it is a Dark Opus Weapon and it has keys
|
// If there is a grid weapon, it is a Dark Opus Weapon and it has keys
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 2 &&
|
gridWeapon.object.series === 2 &&
|
||||||
props.gridWeapon.weapon_keys
|
gridWeapon.weapon_keys
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
props.gridWeapon.weapon_keys[index] &&
|
gridWeapon.weapon_keys[index] &&
|
||||||
props.gridWeapon.weapon_keys[index].slot === 0
|
gridWeapon.weapon_keys[index].slot === 0
|
||||||
) {
|
) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[index].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[index].name[locale]}`
|
||||||
filename = `${props.gridWeapon.weapon_keys[index].slug}.png`
|
filename = `${gridWeapon.weapon_keys[index].slug}.png`
|
||||||
} else if (
|
} else if (
|
||||||
props.gridWeapon.weapon_keys[index] &&
|
gridWeapon.weapon_keys[index] &&
|
||||||
props.gridWeapon.weapon_keys[index].slot === 1
|
gridWeapon.weapon_keys[index].slot === 1
|
||||||
) {
|
) {
|
||||||
altText = `${props.gridWeapon.weapon_keys[index].name[locale]}`
|
altText = `${gridWeapon.weapon_keys[index].name[locale]}`
|
||||||
|
|
||||||
const element = props.gridWeapon.object.element
|
const element = gridWeapon.object.element
|
||||||
const mod = props.gridWeapon.object.name.en.includes('Repudiation')
|
const mod = gridWeapon.object.name.en.includes('Repudiation')
|
||||||
? 'primal'
|
? 'primal'
|
||||||
: 'magna'
|
: 'magna'
|
||||||
|
|
||||||
const suffix = `${mod}-${element}`
|
const suffix = `${mod}-${element}`
|
||||||
const weaponKey = props.gridWeapon.weapon_keys[index]
|
const weaponKey = gridWeapon.weapon_keys[index]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
|
|
@ -250,9 +357,9 @@ const WeaponUnit = (props: Props) => {
|
||||||
'chain-glorification',
|
'chain-glorification',
|
||||||
].includes(weaponKey.slug)
|
].includes(weaponKey.slug)
|
||||||
) {
|
) {
|
||||||
filename = `${props.gridWeapon.weapon_keys[index].slug}-${suffix}.png`
|
filename = `${gridWeapon.weapon_keys[index].slug}-${suffix}.png`
|
||||||
} else {
|
} else {
|
||||||
filename = `${props.gridWeapon.weapon_keys[index].slug}.png`
|
filename = `${gridWeapon.weapon_keys[index].slug}.png`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,12 +377,12 @@ const WeaponUnit = (props: Props) => {
|
||||||
function opusImages() {
|
function opusImages() {
|
||||||
let images: JSX.Element[] = []
|
let images: JSX.Element[] = []
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.series === 2 &&
|
gridWeapon.object.series === 2 &&
|
||||||
props.gridWeapon.weapon_keys &&
|
gridWeapon.weapon_keys &&
|
||||||
props.gridWeapon.weapon_keys.length > 0
|
gridWeapon.weapon_keys.length > 0
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < props.gridWeapon.weapon_keys.length; i++) {
|
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) {
|
||||||
const image = opusImage(i)
|
const image = opusImage(i)
|
||||||
if (image) images.push(image)
|
if (image) images.push(image)
|
||||||
}
|
}
|
||||||
|
|
@ -288,13 +395,13 @@ const WeaponUnit = (props: Props) => {
|
||||||
const axSkill = getCanonicalAxSkill(index)
|
const axSkill = getCanonicalAxSkill(index)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.ax &&
|
gridWeapon.object.ax &&
|
||||||
props.gridWeapon.object.ax_type > 0 &&
|
gridWeapon.object.ax_type > 0 &&
|
||||||
props.gridWeapon.ax &&
|
gridWeapon.ax &&
|
||||||
axSkill
|
axSkill
|
||||||
) {
|
) {
|
||||||
const altText = `${axSkill.name[locale]} Lv${props.gridWeapon.ax[index].strength}`
|
const altText = `${axSkill.name[locale]} Lv${gridWeapon.ax[index].strength}`
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt={altText}
|
alt={altText}
|
||||||
|
|
@ -309,12 +416,12 @@ const WeaponUnit = (props: Props) => {
|
||||||
function axImages() {
|
function axImages() {
|
||||||
let images: JSX.Element[] = []
|
let images: JSX.Element[] = []
|
||||||
if (
|
if (
|
||||||
props.gridWeapon &&
|
gridWeapon &&
|
||||||
props.gridWeapon.object.ax &&
|
gridWeapon.object.ax &&
|
||||||
props.gridWeapon.ax &&
|
gridWeapon.ax &&
|
||||||
props.gridWeapon.ax.length > 0
|
gridWeapon.ax.length > 0
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < props.gridWeapon.ax.length; i++) {
|
for (let i = 0; i < gridWeapon.ax.length; i++) {
|
||||||
const image = axImage(i)
|
const image = axImage(i)
|
||||||
if (image) images.push(image)
|
if (image) images.push(image)
|
||||||
}
|
}
|
||||||
|
|
@ -323,46 +430,86 @@ const WeaponUnit = (props: Props) => {
|
||||||
return images
|
return images
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCanonicalAxSkill(index: number) {
|
// Methods: Layer element rendering
|
||||||
if (
|
const weaponModal = () => {
|
||||||
props.gridWeapon &&
|
if (gridWeapon) {
|
||||||
props.gridWeapon.object.ax &&
|
|
||||||
props.gridWeapon.object.ax_type > 0 &&
|
|
||||||
props.gridWeapon.ax
|
|
||||||
) {
|
|
||||||
const axOptions = axData[props.gridWeapon.object.ax_type - 1]
|
|
||||||
const weaponAxSkill: SimpleAxSkill = props.gridWeapon.ax[0]
|
|
||||||
|
|
||||||
let axSkill = axOptions.find((ax) => ax.id === weaponAxSkill.modifier)
|
|
||||||
|
|
||||||
if (index !== 0 && axSkill && axSkill.secondary) {
|
|
||||||
const weaponSubAxSkill: SimpleAxSkill = props.gridWeapon.ax[1]
|
|
||||||
axSkill = axSkill.secondary.find(
|
|
||||||
(ax) => ax.id === weaponSubAxSkill.modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return axSkill
|
|
||||||
} else return
|
|
||||||
}
|
|
||||||
|
|
||||||
function passUncapData(index: number) {
|
|
||||||
if (props.gridWeapon)
|
|
||||||
props.updateUncap(props.gridWeapon.id, props.position, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
function canBeModified(gridWeapon: GridWeapon) {
|
|
||||||
const weapon = gridWeapon.object
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
weapon.ax ||
|
<WeaponModal
|
||||||
weapon.awakening ||
|
gridWeapon={gridWeapon}
|
||||||
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
|
open={detailsModalOpen}
|
||||||
|
onOpenChange={handleWeaponModalOpenChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextMenu = () => {
|
||||||
|
if (editable && gridWeapon && gridWeapon.id) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContextMenu onOpenChange={handleContextMenuOpenChange}>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
accessoryIcon={<SettingsIcon />}
|
||||||
|
className={buttonClasses}
|
||||||
|
onClick={handleButtonClicked}
|
||||||
|
/>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent align="start">
|
||||||
|
{canBeModified(gridWeapon) ? (
|
||||||
|
<ContextMenuItem onSelect={openWeaponModal}>
|
||||||
|
{t('context.modify.weapon')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<ContextMenuItem onSelect={openRemoveWeaponAlert}>
|
||||||
|
{t('context.remove')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
|
{weaponModal()}
|
||||||
|
{removeAlert()}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeAlert = () => {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
open={alertOpen}
|
||||||
|
primaryAction={removeWeapon}
|
||||||
|
primaryActionText={t('modals.weapon.buttons.remove')}
|
||||||
|
cancelAction={() => setAlertOpen(false)}
|
||||||
|
cancelActionText={t('buttons.cancel')}
|
||||||
|
message={
|
||||||
|
<Trans i18nKey="modals.weapons.messages.remove">
|
||||||
|
Are you sure you want to remove{' '}
|
||||||
|
<strong>{{ weapon: gridWeapon?.object.name[locale] }}</strong> from
|
||||||
|
your team?
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchModal = () => {
|
||||||
|
return (
|
||||||
|
<SearchModal
|
||||||
|
placeholderText={t('search.placeholders.weapon')}
|
||||||
|
fromPosition={position}
|
||||||
|
object="weapons"
|
||||||
|
open={searchModalOpen}
|
||||||
|
onOpenChange={handleSearchModalOpenChange}
|
||||||
|
send={updateObject}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Core element rendering
|
||||||
const image = (
|
const image = (
|
||||||
<div className="WeaponImage">
|
<div className="WeaponImage" onClick={openSearchModal}>
|
||||||
<div className="Modifiers">
|
<div className="Modifiers">
|
||||||
{awakeningImage()}
|
{awakeningImage()}
|
||||||
<div className="Skills">
|
<div className="Skills">
|
||||||
|
|
@ -380,7 +527,7 @@ const WeaponUnit = (props: Props) => {
|
||||||
})}
|
})}
|
||||||
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
||||||
/>
|
/>
|
||||||
{props.editable ? (
|
{editable ? (
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -390,31 +537,12 @@ const WeaponUnit = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const editableImage = (
|
|
||||||
<SearchModal
|
|
||||||
placeholderText={t('search.placeholders.weapon')}
|
|
||||||
fromPosition={props.position}
|
|
||||||
object="weapons"
|
|
||||||
send={props.updateObject}
|
|
||||||
>
|
|
||||||
{image}
|
|
||||||
</SearchModal>
|
|
||||||
)
|
|
||||||
|
|
||||||
const unitContent = (
|
const unitContent = (
|
||||||
|
<>
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{props.editable &&
|
{contextMenu()}
|
||||||
gridWeapon &&
|
{image}
|
||||||
gridWeapon.id &&
|
{gridWeapon && weapon ? (
|
||||||
canBeModified(gridWeapon) ? (
|
|
||||||
<WeaponModal gridWeapon={gridWeapon}>
|
|
||||||
<Button accessoryIcon={<SettingsIcon />} />
|
|
||||||
</WeaponModal>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{props.editable ? editableImage : image}
|
|
||||||
{gridWeapon ? (
|
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="weapon"
|
type="weapon"
|
||||||
ulb={gridWeapon.object.uncap.ulb || false}
|
ulb={gridWeapon.object.uncap.ulb || false}
|
||||||
|
|
@ -428,13 +556,15 @@ const WeaponUnit = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
<h3 className="WeaponName">{weapon?.name[locale]}</h3>
|
<h3 className="WeaponName">{weapon?.name[locale]}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
{searchModal()}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const withHovercard = (
|
const unitContentWithHovercard = (
|
||||||
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
|
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
|
||||||
)
|
)
|
||||||
|
|
||||||
return gridWeapon && !props.editable ? withHovercard : unitContent
|
return gridWeapon && !editable ? unitContentWithHovercard : unitContent
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponUnit
|
export default WeaponUnit
|
||||||
|
|
|
||||||
|
|
@ -6,57 +6,85 @@ export type Awakening = {
|
||||||
ja: string
|
ja: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const characterAwakening: Awakening[] = [
|
export const characterAwakening: ItemSkill[] = [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 1,
|
||||||
name: {
|
name: {
|
||||||
en: 'Balanced',
|
en: 'Balanced',
|
||||||
ja: 'バランス',
|
ja: 'バランス',
|
||||||
},
|
},
|
||||||
},
|
slug: 'balanced',
|
||||||
{
|
minValue: 1,
|
||||||
id: 1,
|
maxValue: 9,
|
||||||
name: {
|
fractional: false,
|
||||||
en: 'Attack',
|
|
||||||
ja: '攻撃',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: {
|
name: {
|
||||||
en: 'Defense',
|
en: 'Attack',
|
||||||
ja: '防御',
|
ja: '攻撃',
|
||||||
},
|
},
|
||||||
|
slug: 'attack',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 9,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
|
name: {
|
||||||
|
en: 'Defense',
|
||||||
|
ja: '防御',
|
||||||
|
},
|
||||||
|
slug: 'defense',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 9,
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
name: {
|
name: {
|
||||||
en: 'Multiattack',
|
en: 'Multiattack',
|
||||||
ja: '連続攻撃',
|
ja: '連続攻撃',
|
||||||
},
|
},
|
||||||
|
slug: 'multiattack',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 9,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const weaponAwakening: Awakening[] = [
|
export const weaponAwakening: ItemSkill[] = [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 1,
|
||||||
name: {
|
name: {
|
||||||
en: 'Attack',
|
en: 'Attack',
|
||||||
ja: '攻撃',
|
ja: '攻撃',
|
||||||
},
|
},
|
||||||
},
|
slug: 'attack',
|
||||||
{
|
minValue: 1,
|
||||||
id: 1,
|
maxValue: 15,
|
||||||
name: {
|
fractional: false,
|
||||||
en: 'Defense',
|
|
||||||
ja: '防御',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
name: {
|
||||||
|
en: 'Defense',
|
||||||
|
ja: '防御',
|
||||||
|
},
|
||||||
|
slug: 'defense',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 15,
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
name: {
|
name: {
|
||||||
en: 'Special',
|
en: 'Special',
|
||||||
ja: '特殊',
|
ja: '特殊',
|
||||||
},
|
},
|
||||||
|
slug: 'special',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 15,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const axData: AxSkill[][] = [
|
const ax: ItemSkill[][] = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -10,6 +10,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3.5,
|
maxValue: 3.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -20,6 +21,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'ca-dmg',
|
slug: 'ca-dmg',
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -31,6 +33,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'da',
|
slug: 'da',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -42,6 +45,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'ta',
|
slug: 'ta',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -53,6 +57,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'skill-cap',
|
slug: 'skill-cap',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -67,6 +72,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 8,
|
maxValue: 8,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -77,6 +83,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'hp',
|
slug: 'hp',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -88,6 +95,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'debuff',
|
slug: 'debuff',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -99,6 +107,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'healing',
|
slug: 'healing',
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -110,6 +119,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'enmity',
|
slug: 'enmity',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -123,6 +133,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 11,
|
maxValue: 11,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -133,6 +144,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'def',
|
slug: 'def',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -144,6 +156,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'debuff',
|
slug: 'debuff',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -156,6 +169,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -166,6 +180,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -179,6 +194,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 8.5,
|
maxValue: 8.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -190,6 +206,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 1.5,
|
maxValue: 1.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -201,6 +218,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -212,6 +230,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -222,6 +241,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -235,6 +255,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -246,6 +267,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -257,6 +279,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -268,6 +291,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -279,6 +303,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -294,6 +319,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3.5,
|
maxValue: 3.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -305,6 +331,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 8.5,
|
maxValue: 8.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -316,6 +343,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1.5,
|
minValue: 1.5,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -327,6 +355,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 0.5,
|
minValue: 0.5,
|
||||||
maxValue: 1.5,
|
maxValue: 1.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -337,6 +366,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'skill-supp',
|
slug: 'skill-supp',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -350,6 +380,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 8,
|
maxValue: 8,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -361,6 +392,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -372,6 +404,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -383,6 +416,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -393,6 +427,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'enmity',
|
slug: 'enmity',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -406,6 +441,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 11,
|
maxValue: 11,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -417,6 +453,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -428,6 +465,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -439,6 +477,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -449,6 +488,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -462,6 +502,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 8.5,
|
maxValue: 8.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -472,6 +513,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'ta',
|
slug: 'ta',
|
||||||
minValue: 1.5,
|
minValue: 1.5,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -483,6 +525,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'skill-supp',
|
slug: 'skill-supp',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -493,6 +536,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'ca-supp',
|
slug: 'ca-supp',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -503,6 +547,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -516,6 +561,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -526,6 +572,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'ca-supp',
|
slug: 'ca-supp',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -536,6 +583,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'na-cap',
|
slug: 'na-cap',
|
||||||
minValue: 0.5,
|
minValue: 0.5,
|
||||||
maxValue: 1.5,
|
maxValue: 1.5,
|
||||||
|
fractional: true,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -547,6 +595,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -557,6 +606,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'enmity',
|
slug: 'enmity',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -572,6 +622,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3.5,
|
maxValue: 3.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -583,6 +634,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -605,6 +657,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -616,6 +669,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -629,6 +683,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 8,
|
maxValue: 8,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -640,6 +695,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -651,6 +707,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -662,6 +719,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -672,6 +730,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'enmity',
|
slug: 'enmity',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -685,6 +744,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 11,
|
maxValue: 11,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -696,6 +756,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -707,6 +768,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -718,6 +780,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -728,6 +791,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -741,6 +805,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 8.5,
|
maxValue: 8.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -752,6 +817,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 1.5,
|
maxValue: 1.5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -763,6 +829,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -774,6 +841,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -784,6 +852,7 @@ export const axData: AxSkill[][] = [
|
||||||
slug: 'stamina',
|
slug: 'stamina',
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -797,6 +866,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
secondary: [
|
secondary: [
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -808,6 +878,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
maxValue: 4,
|
maxValue: 4,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -819,6 +890,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 5,
|
maxValue: 5,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -830,6 +902,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -841,6 +914,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 1,
|
minValue: 1,
|
||||||
maxValue: 2,
|
maxValue: 2,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -854,6 +928,7 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 5,
|
minValue: 5,
|
||||||
maxValue: 10,
|
maxValue: 10,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
|
|
@ -865,6 +940,9 @@ export const axData: AxSkill[][] = [
|
||||||
minValue: 10,
|
minValue: 10,
|
||||||
maxValue: 20,
|
maxValue: 20,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export default ax
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export const allElement: TeamElement = {
|
export const allElement: TeamElement = {
|
||||||
id: -1,
|
id: -1,
|
||||||
|
opposite_id: -1,
|
||||||
name: {
|
name: {
|
||||||
en: 'All',
|
en: 'All',
|
||||||
ja: '全s',
|
ja: '全s',
|
||||||
|
|
@ -9,6 +10,7 @@ export const allElement: TeamElement = {
|
||||||
export const elements: TeamElement[] = [
|
export const elements: TeamElement[] = [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
|
opposite_id: 0,
|
||||||
name: {
|
name: {
|
||||||
en: 'Null',
|
en: 'Null',
|
||||||
ja: '無',
|
ja: '無',
|
||||||
|
|
@ -16,6 +18,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
opposite_id: 4,
|
||||||
name: {
|
name: {
|
||||||
en: 'Wind',
|
en: 'Wind',
|
||||||
ja: '風',
|
ja: '風',
|
||||||
|
|
@ -23,6 +26,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
opposite_id: 1,
|
||||||
name: {
|
name: {
|
||||||
en: 'Fire',
|
en: 'Fire',
|
||||||
ja: '火',
|
ja: '火',
|
||||||
|
|
@ -30,6 +34,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
|
opposite_id: 2,
|
||||||
name: {
|
name: {
|
||||||
en: 'Water',
|
en: 'Water',
|
||||||
ja: '水',
|
ja: '水',
|
||||||
|
|
@ -37,6 +42,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
|
opposite_id: 3,
|
||||||
name: {
|
name: {
|
||||||
en: 'Earth',
|
en: 'Earth',
|
||||||
ja: '土',
|
ja: '土',
|
||||||
|
|
@ -44,6 +50,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
|
opposite_id: 6,
|
||||||
name: {
|
name: {
|
||||||
en: 'Dark',
|
en: 'Dark',
|
||||||
ja: '闇',
|
ja: '闇',
|
||||||
|
|
@ -51,6 +58,7 @@ export const elements: TeamElement[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
|
opposite_id: 5,
|
||||||
name: {
|
name: {
|
||||||
en: 'Light',
|
en: 'Light',
|
||||||
ja: '光',
|
ja: '光',
|
||||||
332
data/overMastery.tsx
Normal file
332
data/overMastery.tsx
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
const overMasteryPrimary: ItemSkill[] = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'ATK',
|
||||||
|
ja: '攻撃',
|
||||||
|
},
|
||||||
|
id: 1,
|
||||||
|
slug: 'atk',
|
||||||
|
minValue: 300,
|
||||||
|
maxValue: 3000,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
values: [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'HP',
|
||||||
|
ja: 'HP',
|
||||||
|
},
|
||||||
|
id: 2,
|
||||||
|
slug: 'hp',
|
||||||
|
minValue: 150,
|
||||||
|
maxValue: 1500,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
values: [150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const overMasterySecondary: ItemSkill[] = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Debuff Success',
|
||||||
|
ja: '弱体成功率',
|
||||||
|
},
|
||||||
|
id: 3,
|
||||||
|
slug: 'debuff-success',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 15,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Skill DMG Cap',
|
||||||
|
ja: 'アビダメ上限',
|
||||||
|
},
|
||||||
|
id: 4,
|
||||||
|
slug: 'skill-cap',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 15,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'C.A. DMG',
|
||||||
|
ja: '奥義ダメージ',
|
||||||
|
},
|
||||||
|
id: 5,
|
||||||
|
slug: 'ca-dmg',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 30,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [10, 12, 14, 16, 18, 20, 22, 24, 27, 30],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'C.A. DMG Cap',
|
||||||
|
ja: '奥義ダメージ上限',
|
||||||
|
},
|
||||||
|
id: 6,
|
||||||
|
slug: 'ca-cap',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 15,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Stamina',
|
||||||
|
ja: '渾身',
|
||||||
|
},
|
||||||
|
id: 7,
|
||||||
|
slug: 'stamina',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Enmity',
|
||||||
|
ja: '背水',
|
||||||
|
},
|
||||||
|
id: 8,
|
||||||
|
slug: 'enmity',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Critical Hit',
|
||||||
|
ja: 'クリティカル確率',
|
||||||
|
},
|
||||||
|
id: 9,
|
||||||
|
slug: 'crit',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 30,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [10, 12, 14, 16, 18, 20, 22, 24, 27, 30],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const overMasteryTertiary: ItemSkill[] = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Double Attack',
|
||||||
|
ja: 'ダブルアタック確率',
|
||||||
|
},
|
||||||
|
id: 10,
|
||||||
|
slug: 'da',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 15,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Triple Attack',
|
||||||
|
ja: 'トリプルアタック確率',
|
||||||
|
},
|
||||||
|
id: 11,
|
||||||
|
slug: 'ta',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'DEF',
|
||||||
|
ja: '防御',
|
||||||
|
},
|
||||||
|
id: 12,
|
||||||
|
slug: 'def',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 20,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 12, 14, 16, 18, 20],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Healing',
|
||||||
|
ja: '回復性能',
|
||||||
|
},
|
||||||
|
id: 13,
|
||||||
|
slug: 'heal',
|
||||||
|
minValue: 3,
|
||||||
|
maxValue: 30,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Debuff Resistance',
|
||||||
|
ja: '弱体耐性',
|
||||||
|
},
|
||||||
|
id: 14,
|
||||||
|
slug: 'debuff-resist',
|
||||||
|
minValue: 6,
|
||||||
|
maxValue: 15,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Dodge',
|
||||||
|
ja: '回避',
|
||||||
|
},
|
||||||
|
id: 15,
|
||||||
|
slug: 'dodge',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const overMastery = {
|
||||||
|
a: overMasteryPrimary,
|
||||||
|
b: overMasterySecondary,
|
||||||
|
c: overMasteryTertiary,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const aetherialMastery: ItemSkill[] = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Double Attack',
|
||||||
|
ja: 'ダブルアタック確率',
|
||||||
|
},
|
||||||
|
id: 1,
|
||||||
|
slug: 'da',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 17,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Triple Attack',
|
||||||
|
ja: 'トリプルアタック確率',
|
||||||
|
},
|
||||||
|
id: 2,
|
||||||
|
slug: 'ta',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: '{Element} ATK Up',
|
||||||
|
ja: '{属性}攻撃',
|
||||||
|
},
|
||||||
|
id: 3,
|
||||||
|
slug: 'element-atk',
|
||||||
|
minValue: 15,
|
||||||
|
maxValue: 22,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: '{Element} Resistance',
|
||||||
|
ja: '{属性}軽減',
|
||||||
|
},
|
||||||
|
id: 4,
|
||||||
|
slug: 'element-resist',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Stamina',
|
||||||
|
ja: '渾身',
|
||||||
|
},
|
||||||
|
id: 5,
|
||||||
|
slug: 'stamina',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Enmity',
|
||||||
|
ja: '背水',
|
||||||
|
},
|
||||||
|
id: 6,
|
||||||
|
slug: 'enmity',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Supplemental DMG',
|
||||||
|
ja: '与ダメ上昇',
|
||||||
|
},
|
||||||
|
id: 7,
|
||||||
|
slug: 'supplemental',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Critical Hit',
|
||||||
|
ja: 'クリティカル',
|
||||||
|
},
|
||||||
|
id: 8,
|
||||||
|
slug: 'crit',
|
||||||
|
minValue: 18,
|
||||||
|
maxValue: 35,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Counters on Dodge',
|
||||||
|
ja: 'カウンター(回避)',
|
||||||
|
},
|
||||||
|
id: 9,
|
||||||
|
slug: 'counter-dodge',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 12,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Counters on DMG',
|
||||||
|
ja: 'カウンター(被ダメ)',
|
||||||
|
},
|
||||||
|
id: 10,
|
||||||
|
slug: 'counter-dmg',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 17,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
56
hooks/useLockedBody.tsx
Normal file
56
hooks/useLockedBody.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// https://usehooks-ts.com/react-hook/use-locked-body
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useIsomorphicLayoutEffect } from 'usehooks-ts'
|
||||||
|
|
||||||
|
type UseLockedBodyOutput = [boolean, (locked: boolean) => void]
|
||||||
|
|
||||||
|
function useLockedBody(
|
||||||
|
initialLocked = false,
|
||||||
|
rootId = '___gatsby' // Default to `___gatsby` to not introduce breaking change
|
||||||
|
): UseLockedBodyOutput {
|
||||||
|
const [locked, setLocked] = useState(initialLocked)
|
||||||
|
|
||||||
|
// Do the side effect before render
|
||||||
|
useIsomorphicLayoutEffect(() => {
|
||||||
|
if (!locked) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save initial body style
|
||||||
|
const originalOverflow = document.body.style.overflow
|
||||||
|
const originalPaddingRight = document.body.style.paddingRight
|
||||||
|
|
||||||
|
// Lock body scroll
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
|
||||||
|
// Get the scrollBar width
|
||||||
|
const root = document.getElementById(rootId) // or root
|
||||||
|
const scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0
|
||||||
|
|
||||||
|
// Avoid width reflow
|
||||||
|
if (scrollBarWidth) {
|
||||||
|
document.body.style.paddingRight = `${scrollBarWidth}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = originalOverflow
|
||||||
|
|
||||||
|
if (scrollBarWidth) {
|
||||||
|
document.body.style.paddingRight = originalPaddingRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [locked])
|
||||||
|
|
||||||
|
// Update state if initialValue changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (locked !== initialLocked) {
|
||||||
|
setLocked(initialLocked)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [initialLocked])
|
||||||
|
|
||||||
|
return [locked, setLocked]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLockedBody
|
||||||
20
package-lock.json
generated
20
package-lock.json
generated
|
|
@ -40,6 +40,7 @@
|
||||||
"react-string-replace": "^1.1.0",
|
"react-string-replace": "^1.1.0",
|
||||||
"sanitize-html": "^2.8.1",
|
"sanitize-html": "^2.8.1",
|
||||||
"sass": "^1.49.0",
|
"sass": "^1.49.0",
|
||||||
|
"usehooks-ts": "^2.9.1",
|
||||||
"valtio": "^1.3.0",
|
"valtio": "^1.3.0",
|
||||||
"youtube-api-v3-wrapper": "^2.3.0"
|
"youtube-api-v3-wrapper": "^2.3.0"
|
||||||
},
|
},
|
||||||
|
|
@ -7136,6 +7137,19 @@
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/usehooks-ts": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.15.0",
|
||||||
|
"npm": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
@ -12203,6 +12217,12 @@
|
||||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"usehooks-ts": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
"react-string-replace": "^1.1.0",
|
"react-string-replace": "^1.1.0",
|
||||||
"sanitize-html": "^2.8.1",
|
"sanitize-html": "^2.8.1",
|
||||||
"sass": "^1.49.0",
|
"sass": "^1.49.0",
|
||||||
|
"usehooks-ts": "^2.9.1",
|
||||||
"valtio": "^1.3.0",
|
"valtio": "^1.3.0",
|
||||||
"youtube-api-v3-wrapper": "^2.3.0"
|
"youtube-api-v3-wrapper": "^2.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
import { elements, allElement } from '~utils/Element'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
|
|
||||||
import GridRep from '~components/GridRep'
|
import GridRep from '~components/GridRep'
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
import { elements, allElement } from '~utils/Element'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
|
|
||||||
import GridRep from '~components/GridRep'
|
import GridRep from '~components/GridRep'
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
import { elements, allElement } from '~utils/Element'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
|
|
||||||
import GridRep from '~components/GridRep'
|
import GridRep from '~components/GridRep'
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@
|
||||||
"new": "New",
|
"new": "New",
|
||||||
"wiki": "View more on gbf.wiki"
|
"wiki": "View more on gbf.wiki"
|
||||||
},
|
},
|
||||||
|
"context": {
|
||||||
|
"modify": {
|
||||||
|
"character": "Modify character",
|
||||||
|
"summon": "Modify summon",
|
||||||
|
"weapon": "Modify weapon"
|
||||||
|
},
|
||||||
|
"remove": "Remove from grid"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"element": "Element",
|
"element": "Element",
|
||||||
|
|
@ -137,6 +145,22 @@
|
||||||
"changelog": {
|
"changelog": {
|
||||||
"title": "Changelog"
|
"title": "Changelog"
|
||||||
},
|
},
|
||||||
|
"characters": {
|
||||||
|
"title": "Character",
|
||||||
|
"subtitles": {
|
||||||
|
"ring": "Over Mastery",
|
||||||
|
"earring": "Aetherial Mastery",
|
||||||
|
"permanent": "Permanent Mastery",
|
||||||
|
"awakening": "Awakening"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"remove": "Are you sure you want to remove <strong>{{character}}</strong> from your team?"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"confirm": "Save character",
|
||||||
|
"remove": "Remove character"
|
||||||
|
}
|
||||||
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"character": "Only one <strong>version of a character</strong> can be included in each party. Do you want to change your party members?",
|
"character": "Only one <strong>version of a character</strong> can be included in each party. Do you want to change your party members?",
|
||||||
"weapon": {
|
"weapon": {
|
||||||
|
|
@ -229,7 +253,8 @@
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"title": "Modify Weapon",
|
"title": "Modify Weapon",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"confirm": "Save weapon"
|
"confirm": "Save weapon",
|
||||||
|
"remove": "Remove weapon"
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"element": "Element",
|
"element": "Element",
|
||||||
|
|
@ -339,5 +364,6 @@
|
||||||
"no_title": "Untitled",
|
"no_title": "Untitled",
|
||||||
"no_raid": "No raid",
|
"no_raid": "No raid",
|
||||||
"no_user": "Anonymous",
|
"no_user": "Anonymous",
|
||||||
"no_job": "No class"
|
"no_job": "No class",
|
||||||
|
"no_value": "No value"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@
|
||||||
"new": "作成",
|
"new": "作成",
|
||||||
"wiki": "gbf.wikiで詳しく見る"
|
"wiki": "gbf.wikiで詳しく見る"
|
||||||
},
|
},
|
||||||
|
"context": {
|
||||||
|
"modify": {
|
||||||
|
"character": "キャラクターを変更",
|
||||||
|
"summon": "召喚石を変更",
|
||||||
|
"weapon": "武器を変更"
|
||||||
|
},
|
||||||
|
"remove": "編成から削除"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"element": "属性",
|
"element": "属性",
|
||||||
|
|
@ -137,6 +145,22 @@
|
||||||
"changelog": {
|
"changelog": {
|
||||||
"title": "変更ログ"
|
"title": "変更ログ"
|
||||||
},
|
},
|
||||||
|
"characters": {
|
||||||
|
"title": "キャラクター",
|
||||||
|
"subtitles": {
|
||||||
|
"ring": "EXリミットボーナス",
|
||||||
|
"earring": "エーテリアルプラス",
|
||||||
|
"permanent": "マスタリーボーナス",
|
||||||
|
"awakening": "覚醒"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"remove": "<strong>{{character}}</strong>を編成から削除しますか?"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"confirm": "キャラクターを変更する",
|
||||||
|
"remove": "キャラクターを削除する"
|
||||||
|
}
|
||||||
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"character": "<strong>同じ名前のキャラクター</strong>がパーティに編成されています。<br />以下のキャラクターを入れ替えますか?",
|
"character": "<strong>同じ名前のキャラクター</strong>がパーティに編成されています。<br />以下のキャラクターを入れ替えますか?",
|
||||||
"weapon": {
|
"weapon": {
|
||||||
|
|
@ -230,7 +254,8 @@
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"title": "武器変更",
|
"title": "武器変更",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"confirm": "武器を変更する"
|
"confirm": "武器を変更する",
|
||||||
|
"remove": "武器を削除する"
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"element": "属性",
|
"element": "属性",
|
||||||
|
|
@ -340,5 +365,6 @@
|
||||||
"no_title": "無題",
|
"no_title": "無題",
|
||||||
"no_raid": "マルチなし",
|
"no_raid": "マルチなし",
|
||||||
"no_user": "無名",
|
"no_user": "無名",
|
||||||
"no_job": "ジョブなし"
|
"no_job": "ジョブなし",
|
||||||
|
"no_value": "値なし"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ $dialog--bg--dark: $grey-25;
|
||||||
|
|
||||||
// Color Definitions: Menu
|
// Color Definitions: Menu
|
||||||
$menu--bg--light: $grey-100;
|
$menu--bg--light: $grey-100;
|
||||||
$menu--bg--dark: $grey-05;
|
$menu--bg--dark: $grey-10;
|
||||||
$menu--text--light: $grey-90;
|
$menu--text--light: $grey-90;
|
||||||
$menu--text--dark: $grey-50;
|
$menu--text--dark: $grey-50;
|
||||||
$menu--separator--light: $grey-90;
|
$menu--separator--light: $grey-90;
|
||||||
|
|
@ -307,3 +307,7 @@ $item-corner: $unit-half;
|
||||||
// Shadows
|
// Shadows
|
||||||
$hover-stroke: 1px solid rgba(0, 0, 0, 0.1);
|
$hover-stroke: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
$hover-shadow: rgba(0, 0, 0, 0.08) 0px 0px 14px;
|
$hover-shadow: rgba(0, 0, 0, 0.08) 0px 0px 14px;
|
||||||
|
|
||||||
|
// Durations
|
||||||
|
$duration-color-fade: 0.24s;
|
||||||
|
$duration-zoom: 0.18s;
|
||||||
|
|
|
||||||
3
types/GridCharacter.d.ts
vendored
3
types/GridCharacter.d.ts
vendored
|
|
@ -3,8 +3,11 @@ interface GridCharacter {
|
||||||
position: number
|
position: number
|
||||||
object: Character
|
object: Character
|
||||||
uncap_level: number
|
uncap_level: number
|
||||||
|
over_mastery: CharacterOverMastery
|
||||||
|
aetherial_mastery: ExtendedMastery
|
||||||
awakening: {
|
awakening: {
|
||||||
type: number
|
type: number
|
||||||
level: number
|
level: number
|
||||||
}
|
}
|
||||||
|
perpetuity: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
types/AxSkill.d.ts → types/ItemSkill.d.ts
vendored
6
types/AxSkill.d.ts → types/ItemSkill.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
interface AxSkill {
|
interface ItemSkill {
|
||||||
name: {
|
name: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
en: string
|
en: string
|
||||||
|
|
@ -8,6 +8,8 @@ interface AxSkill {
|
||||||
slug: string
|
slug: string
|
||||||
minValue: number
|
minValue: number
|
||||||
maxValue: number
|
maxValue: number
|
||||||
|
fractional: boolean
|
||||||
suffix?: string
|
suffix?: string
|
||||||
secondary?: AxSkill[]
|
secondary?: ItemSkill[]
|
||||||
|
values?: number[]
|
||||||
}
|
}
|
||||||
1
types/TeamElement.d.ts
vendored
1
types/TeamElement.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
interface TeamElement {
|
interface TeamElement {
|
||||||
id: number
|
id: number
|
||||||
|
opposite_id: number
|
||||||
name: {
|
name: {
|
||||||
en: string
|
en: string
|
||||||
ja: string
|
ja: string
|
||||||
|
|
|
||||||
13
types/index.d.ts
vendored
13
types/index.d.ts
vendored
|
|
@ -35,3 +35,16 @@ export type DetailsObject = {
|
||||||
job?: Job
|
job?: Job
|
||||||
extra?: boolean
|
extra?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExtendedMastery = {
|
||||||
|
modifier?: number
|
||||||
|
strength?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CharacterOverMastery = {
|
||||||
|
[key: number]: ExtendedMastery
|
||||||
|
1: ExtendedMastery
|
||||||
|
2: ExtendedMastery
|
||||||
|
3: ExtendedMastery
|
||||||
|
4: ExtendedMastery
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ class Api {
|
||||||
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
||||||
api.createEntity({ name: 'users' })
|
api.createEntity({ name: 'users' })
|
||||||
api.createEntity({ name: 'parties' })
|
api.createEntity({ name: 'parties' })
|
||||||
|
api.createEntity({ name: 'grid_characters' })
|
||||||
api.createEntity({ name: 'grid_weapons' })
|
api.createEntity({ name: 'grid_weapons' })
|
||||||
api.createEntity({ name: 'characters' })
|
api.createEntity({ name: 'characters' })
|
||||||
api.createEntity({ name: 'weapons' })
|
api.createEntity({ name: 'weapons' })
|
||||||
|
|
|
||||||
54
utils/elementalizeAetherialMastery.tsx
Normal file
54
utils/elementalizeAetherialMastery.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { elements } from '~data/elements'
|
||||||
|
import { aetherialMastery } from '~data/overMastery'
|
||||||
|
|
||||||
|
export default function elementalizeAetherialMastery(
|
||||||
|
gridCharacter: GridCharacter
|
||||||
|
) {
|
||||||
|
const elementalized = aetherialMastery.map((modifier) => {
|
||||||
|
const element = elements.find((a) => a.id === gridCharacter.object.element)
|
||||||
|
|
||||||
|
const oppositeElement = elements.find((b) => {
|
||||||
|
if (element) return b.id === element.opposite_id
|
||||||
|
})
|
||||||
|
|
||||||
|
const newModifier = modifier
|
||||||
|
|
||||||
|
if (element && oppositeElement && modifier.name.en.includes('{Element}')) {
|
||||||
|
if (modifier.id === 3) {
|
||||||
|
newModifier.name.en = newModifier.name.en.replace(
|
||||||
|
'{Element}',
|
||||||
|
element.name.en
|
||||||
|
)
|
||||||
|
newModifier.name.ja = newModifier.name.ja.replace(
|
||||||
|
'{属性}',
|
||||||
|
`${element.name.ja}属性`
|
||||||
|
)
|
||||||
|
} else if (modifier.id === 4) {
|
||||||
|
newModifier.name.en = newModifier.name.en.replace(
|
||||||
|
'{Element}',
|
||||||
|
oppositeElement.name.en
|
||||||
|
)
|
||||||
|
newModifier.name.ja = newModifier.name.ja.replace(
|
||||||
|
'{属性}',
|
||||||
|
`${oppositeElement.name.ja}属性`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newModifier
|
||||||
|
})
|
||||||
|
|
||||||
|
elementalized.unshift({
|
||||||
|
id: 0,
|
||||||
|
name: {
|
||||||
|
en: 'No aetherial mastery',
|
||||||
|
ja: 'エーテリアルプラス',
|
||||||
|
},
|
||||||
|
slug: 'no-mastery',
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 0,
|
||||||
|
fractional: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return elementalized
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { elements, allElement } from '~utils/Element'
|
import { elements, allElement } from '~data/elements'
|
||||||
|
|
||||||
export default (query: { [index: string]: string }, raids: Raid[]) => {
|
export default (query: { [index: string]: string }, raids: Raid[]) => {
|
||||||
// Extract recency filter
|
// Extract recency filter
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { weaponKeyGroups } from './weaponKeyGroups'
|
import { weaponKeyGroups } from '../data/weaponKeyGroups'
|
||||||
|
|
||||||
export type GroupedWeaponKeys = {
|
export type GroupedWeaponKeys = {
|
||||||
[key: string]: WeaponKey[]
|
[key: string]: WeaponKey[]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { weaponSeries } from '~utils/weaponSeries'
|
import { weaponSeries } from '~data/weaponSeries'
|
||||||
|
|
||||||
export default (id: number) =>
|
export default (id: number) =>
|
||||||
weaponSeries.find((series) => series.id === id)?.slug
|
weaponSeries.find((series) => series.id === id)?.slug
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue