Merge pull request #20 from jedmund/weapon-mods
Add the ability to add modifications to grid weapons
This commit is contained in:
commit
25bf58da2b
30 changed files with 1903 additions and 42 deletions
48
components/AxSelect/index.scss
Normal file
48
components/AxSelect/index.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
.AXSelect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
|
||||
.AXSet {
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.errors {
|
||||
color: $error;
|
||||
display: none;
|
||||
padding: $unit 0;
|
||||
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
|
||||
select {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.Input {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
border: none;
|
||||
background-color: $grey-90;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
color: $grey-00;
|
||||
height: $unit * 6;
|
||||
display: block;
|
||||
font-size: $font-regular;
|
||||
padding: $unit;
|
||||
text-align: right;
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
243
components/AxSelect/index.tsx
Normal file
243
components/AxSelect/index.tsx
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { axData } from '~utils/axData'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface ErrorMap {
|
||||
[index: string]: string
|
||||
axValue1: string
|
||||
axValue2: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
axType: number
|
||||
currentSkills?: SimpleAxSkill[],
|
||||
sendValidity: (isValid: boolean) => void
|
||||
sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void
|
||||
}
|
||||
|
||||
const AXSelect = (props: Props) => {
|
||||
// Set up form states and error handling
|
||||
const [errors, setErrors] = useState<ErrorMap>({
|
||||
axValue1: '',
|
||||
axValue2: ''
|
||||
})
|
||||
|
||||
const primaryErrorClasses = classNames({
|
||||
'errors': true,
|
||||
'visible': errors.axValue1.length > 0
|
||||
})
|
||||
|
||||
const secondaryErrorClasses = classNames({
|
||||
'errors': true,
|
||||
'visible': errors.axValue2.length > 0
|
||||
})
|
||||
|
||||
// Refs
|
||||
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||
const primaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||
const secondaryAxModifierSelect = React.createRef<HTMLSelectElement>()
|
||||
const secondaryAxValueInput = React.createRef<HTMLInputElement>()
|
||||
|
||||
// States
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||
|
||||
useEffect(() => {
|
||||
if (props.currentSkills && props.currentSkills[0]) {
|
||||
if (props.currentSkills[0].modifier != null)
|
||||
setPrimaryAxModifier(props.currentSkills[0].modifier)
|
||||
|
||||
setPrimaryAxValue(props.currentSkills[0].strength)
|
||||
}
|
||||
|
||||
if (props.currentSkills && props.currentSkills[1]) {
|
||||
if (props.currentSkills[1].modifier != null)
|
||||
setSecondaryAxModifier(props.currentSkills[1].modifier)
|
||||
|
||||
setSecondaryAxValue(props.currentSkills[1].strength)
|
||||
}
|
||||
}, [props.currentSkills])
|
||||
|
||||
useEffect(() => {
|
||||
props.sendValues(primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue)
|
||||
}, [props, primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue])
|
||||
|
||||
useEffect(() => {
|
||||
props.sendValidity(primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === '')
|
||||
}, [props, primaryAxValue, errors])
|
||||
|
||||
// Classes
|
||||
const secondarySetClasses = classNames({
|
||||
'AXSet': true,
|
||||
'hidden': primaryAxModifier < 0
|
||||
})
|
||||
|
||||
function generateOptions(modifierSet: number) {
|
||||
const axOptions = axData[props.axType - 1]
|
||||
|
||||
let axOptionElements: React.ReactNode[] = []
|
||||
if (modifierSet == 0) {
|
||||
axOptionElements = axOptions.map((ax, i) => {
|
||||
return (
|
||||
<option key={i} value={ax.id}>{ax.name.en}</option>
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// If we are loading data from the server, state doesn't set before render,
|
||||
// so our defaultValue is undefined.
|
||||
let modifier = -1;
|
||||
if (primaryAxModifier >= 0)
|
||||
modifier = primaryAxModifier
|
||||
else if (props.currentSkills)
|
||||
modifier = props.currentSkills[0].modifier
|
||||
|
||||
if (modifier >= 0 && axOptions[modifier]) {
|
||||
const primarySkill = axOptions[modifier]
|
||||
|
||||
if (primarySkill.secondary) {
|
||||
const secondaryAxOptions = primarySkill.secondary
|
||||
axOptionElements = secondaryAxOptions.map((ax, i) => {
|
||||
return (
|
||||
<option key={i} value={ax.id}>{ax.name.en}</option>
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
axOptionElements?.unshift(<option key={-1} value={-1}>No AX Skill</option>)
|
||||
return axOptionElements
|
||||
}
|
||||
|
||||
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const value = parseInt(event.target.value)
|
||||
|
||||
if (primaryAxModifierSelect.current == event.target) {
|
||||
setPrimaryAxModifier(value)
|
||||
|
||||
if (primaryAxValueInput.current && secondaryAxModifierSelect.current && secondaryAxValueInput.current) {
|
||||
setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
|
||||
|
||||
secondaryAxModifierSelect.current.value = "-1"
|
||||
secondaryAxValueInput.current.value = ""
|
||||
}
|
||||
} else {
|
||||
setSecondaryAxModifier(value)
|
||||
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
const currentAxSkill = (primaryAxSkill.secondary) ?
|
||||
primaryAxSkill.secondary.find(skill => skill.id == value) : undefined
|
||||
|
||||
if (secondaryAxValueInput.current)
|
||||
setupInput(currentAxSkill, secondaryAxValueInput.current)
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseFloat(event.target.value)
|
||||
let newErrors = {...errors}
|
||||
|
||||
if (primaryAxValueInput.current == event.target) {
|
||||
if (handlePrimaryErrors(value))
|
||||
setPrimaryAxValue(value)
|
||||
} else {
|
||||
if (handleSecondaryErrors(value))
|
||||
setSecondaryAxValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
function handlePrimaryErrors(value: number) {
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
let newErrors = {...errors}
|
||||
|
||||
if (value < primaryAxSkill.minValue) {
|
||||
newErrors.axValue1 = `${primaryAxSkill.name.en} must be at least ${primaryAxSkill.minValue}${ (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''}`
|
||||
} else if (value > primaryAxSkill.maxValue) {
|
||||
newErrors.axValue1 = `${primaryAxSkill.name.en} cannot be greater than ${primaryAxSkill.maxValue}${ (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''}`
|
||||
} else if (!value || value <= 0) {
|
||||
newErrors.axValue1 = `${primaryAxSkill.name.en} must have a value`
|
||||
} else {
|
||||
newErrors.axValue1 = ''
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
|
||||
return newErrors.axValue1.length === 0
|
||||
}
|
||||
|
||||
function handleSecondaryErrors(value: number) {
|
||||
const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
|
||||
let newErrors = {...errors}
|
||||
|
||||
if (primaryAxSkill.secondary) {
|
||||
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier)
|
||||
|
||||
if (secondaryAxSkill) {
|
||||
if (value < secondaryAxSkill.minValue) {
|
||||
newErrors.axValue2 = `${secondaryAxSkill.name.en} must be at least ${secondaryAxSkill.minValue}${ (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''}`
|
||||
} else if (value > secondaryAxSkill.maxValue) {
|
||||
newErrors.axValue2 = `${secondaryAxSkill.name.en} cannot be greater than ${secondaryAxSkill.maxValue}${ (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''}`
|
||||
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
|
||||
newErrors.axValue2 = `${secondaryAxSkill.name.en} must be a whole number`
|
||||
} else if (primaryAxValue <= 0) {
|
||||
newErrors.axValue1 = `${primaryAxSkill.name.en} must have a value`
|
||||
} else {
|
||||
newErrors.axValue2 = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
|
||||
return newErrors.axValue2.length === 0
|
||||
}
|
||||
|
||||
function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
|
||||
if (ax) {
|
||||
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
|
||||
|
||||
element.disabled = false
|
||||
element.placeholder = rangeString
|
||||
element.min = `${ax.minValue}`
|
||||
element.max = `${ax.maxValue}`
|
||||
element.step = (ax.suffix) ? "0.5" : "1"
|
||||
} else {
|
||||
if (primaryAxValueInput.current && secondaryAxValueInput.current) {
|
||||
if (primaryAxValueInput.current == element) {
|
||||
primaryAxValueInput.current.disabled = true
|
||||
primaryAxValueInput.current.placeholder = ''
|
||||
}
|
||||
|
||||
secondaryAxValueInput.current.disabled = true
|
||||
secondaryAxValueInput.current.placeholder = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="AXSelect">
|
||||
<div className="AXSet">
|
||||
<div className="fields">
|
||||
<select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select>
|
||||
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled />
|
||||
</div>
|
||||
<p className={primaryErrorClasses}>{errors.axValue1}</p>
|
||||
</div>
|
||||
|
||||
<div className={secondarySetClasses}>
|
||||
<div className="fields">
|
||||
<select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select>
|
||||
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled />
|
||||
</div>
|
||||
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AXSelect
|
||||
|
|
@ -86,6 +86,11 @@
|
|||
fill: none;
|
||||
stroke: $grey-50;
|
||||
}
|
||||
|
||||
&.settings svg {
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Active {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import EditIcon from '~public/icons/Edit.svg'
|
|||
import LinkIcon from '~public/icons/Link.svg'
|
||||
import MenuIcon from '~public/icons/Menu.svg'
|
||||
import SaveIcon from '~public/icons/Save.svg'
|
||||
import SettingsIcon from '~public/icons/Settings.svg'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
|
|
@ -68,6 +69,10 @@ class Button extends React.Component<Props, State> {
|
|||
icon = <span className='icon stroke'>
|
||||
<SaveIcon />
|
||||
</span>
|
||||
} else if (this.props.icon === 'settings') {
|
||||
icon = <span className='icon settings'>
|
||||
<SettingsIcon />
|
||||
</span>
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
|
|
|
|||
59
components/ElementToggle/index.scss
Normal file
59
components/ElementToggle/index.scss
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
.ToggleGroup {
|
||||
$height: 36px;
|
||||
|
||||
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||
border-radius: $height;
|
||||
display: flex;
|
||||
height: $height;
|
||||
gap: $unit / 4;
|
||||
padding: $unit / 2;
|
||||
|
||||
.ToggleItem {
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
color: $grey-40;
|
||||
flex-grow: 1;
|
||||
font-size: $font-regular;
|
||||
padding: ($unit) $unit * 2;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover, &[data-state="on"] {
|
||||
background:$grey-80;
|
||||
color: $grey-00;
|
||||
|
||||
&.fire {
|
||||
background: $fire-bg-light;
|
||||
color: $fire-text-dark;
|
||||
}
|
||||
|
||||
&.water {
|
||||
background: $water-bg-light;
|
||||
color: $water-text-dark;
|
||||
}
|
||||
|
||||
&.earth {
|
||||
background: $earth-bg-light;
|
||||
color: $earth-text-dark;
|
||||
}
|
||||
|
||||
&.wind {
|
||||
background: $wind-bg-light;
|
||||
color: $wind-text-dark;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: $dark-bg-light;
|
||||
color: $dark-text-dark;
|
||||
}
|
||||
|
||||
&.light {
|
||||
background: $light-bg-light;
|
||||
color: $light-text-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
components/ElementToggle/index.tsx
Normal file
39
components/ElementToggle/index.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react'
|
||||
import * as ToggleGroup from '@radix-ui/react-toggle-group'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
currentElement: number
|
||||
sendValue: (value: string) => void
|
||||
}
|
||||
|
||||
const ElementToggle = (props: Props) => {
|
||||
return (
|
||||
<ToggleGroup.Root className="ToggleGroup" type="single" defaultValue={`${props.currentElement}`} aria-label="Element" onValueChange={props.sendValue}>
|
||||
<ToggleGroup.Item className="ToggleItem" value="0" aria-label="null">
|
||||
Null
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem wind" value="1" aria-label="wind">
|
||||
Wind
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem fire" value="2" aria-label="fire">
|
||||
Fire
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem water" value="3" aria-label="water">
|
||||
Water
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem earth" value="4" aria-label="earth">
|
||||
Earth
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem dark" value="5" aria-label="dark">
|
||||
Dark
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item className="ToggleItem light" value="6" aria-label="light">
|
||||
Light
|
||||
</ToggleGroup.Item>
|
||||
</ToggleGroup.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default ElementToggle
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
background: url('/icons/Arrow.svg'), $grey-90;
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: 98%;
|
||||
background-position-x: 95%;
|
||||
background-size: $unit * 1.5;
|
||||
color: $grey-50;
|
||||
font-size: $font-small;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,16 @@ interface Props {
|
|||
}
|
||||
|
||||
const PartySegmentedControl = (props: Props) => {
|
||||
const { party } = useSnapshot(appState)
|
||||
const { party, grid } = useSnapshot(appState)
|
||||
|
||||
function getElement() {
|
||||
switch(party.element) {
|
||||
let element: number = 0
|
||||
if (party.element == 0 && grid.weapons.mainWeapon)
|
||||
element = grid.weapons.mainWeapon.element
|
||||
else
|
||||
element = party.element
|
||||
|
||||
switch(element) {
|
||||
case 1: return "wind"; break
|
||||
case 2: return "fire"; break
|
||||
case 3: return "water"; break
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
|
||||
import { appState } from '~utils/appState'
|
||||
import api from '~utils/api'
|
||||
|
|
@ -15,7 +14,6 @@ interface Props {
|
|||
}
|
||||
|
||||
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
||||
const [cookies] = useCookies(['user'])
|
||||
const [raids, setRaids] = useState<Raid[][]>()
|
||||
|
||||
const raidGroups = [
|
||||
|
|
@ -50,12 +48,8 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
|
|||
}, [props.allOption])
|
||||
|
||||
useEffect(() => {
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: { 'Authorization': `Bearer ${cookies.user.access_token}` }
|
||||
} : {}
|
||||
|
||||
function fetchRaids() {
|
||||
api.endpoints.raids.getAll(headers)
|
||||
api.endpoints.raids.getAll()
|
||||
.then((response) => {
|
||||
const raids = response.data.map((r: any) => r.raid)
|
||||
|
||||
|
|
@ -65,7 +59,7 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
|
|||
}
|
||||
|
||||
fetchRaids()
|
||||
}, [cookies.user, organizeRaids])
|
||||
}, [organizeRaids])
|
||||
|
||||
function raidGroup(index: number) {
|
||||
const options = raids && raids.length > 0 && raids[index].length > 0 &&
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ const WeaponGrid = (props: Props) => {
|
|||
// Render: JSX components
|
||||
const mainhandElement = (
|
||||
<WeaponUnit
|
||||
gridWeapon={grid.weapons.mainWeapon}
|
||||
gridWeapon={appState.grid.weapons.mainWeapon}
|
||||
editable={party.editable}
|
||||
key="grid_mainhand"
|
||||
position={-1}
|
||||
|
|
@ -247,7 +247,7 @@ const WeaponGrid = (props: Props) => {
|
|||
return (
|
||||
<li key={`grid_unit_${i}`} >
|
||||
<WeaponUnit
|
||||
gridWeapon={grid.weapons.allWeapons[i]}
|
||||
gridWeapon={appState.grid.weapons.allWeapons[i]}
|
||||
editable={party.editable}
|
||||
position={i}
|
||||
unitType={1}
|
||||
|
|
@ -261,7 +261,7 @@ const WeaponGrid = (props: Props) => {
|
|||
|
||||
const extraGridElement = (
|
||||
<ExtraWeapons
|
||||
grid={grid.weapons.allWeapons}
|
||||
grid={appState.grid.weapons.allWeapons}
|
||||
editable={party.editable}
|
||||
offset={numWeapons}
|
||||
updateObject={receiveWeaponFromSearch}
|
||||
|
|
|
|||
0
components/WeaponKeyDropdown/index.scss
Normal file
0
components/WeaponKeyDropdown/index.scss
Normal file
140
components/WeaponKeyDropdown/index.tsx
Normal file
140
components/WeaponKeyDropdown/index.tsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import api from '~utils/api'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
currentValue?: WeaponKey
|
||||
series: number
|
||||
slot: number
|
||||
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
}
|
||||
|
||||
const WeaponKeyDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
||||
const [keys, setKeys] = useState<WeaponKey[][]>([])
|
||||
|
||||
const pendulumNames = [
|
||||
{ en: 'Pendulum', jp: '' },
|
||||
{ en: 'Chain', jp: '' }
|
||||
]
|
||||
|
||||
const telumaNames = [ { en: 'Teluma', jp: '' } ]
|
||||
const emblemNames = [ { en: 'Emblem', jp: '' } ]
|
||||
const gauphNames = [
|
||||
{ en: 'Gauph Key', jp: '' },
|
||||
{ en: 'Ultima Key', jp: '' },
|
||||
{ en: 'Gate of Omnipotence', jp: '' }
|
||||
]
|
||||
|
||||
const keyName = () => {
|
||||
return {
|
||||
en: '',
|
||||
jp: ''
|
||||
}
|
||||
}
|
||||
|
||||
const emptyKey: WeaponKey = {
|
||||
id: '0',
|
||||
name: {
|
||||
en: `No ${keyName().en}`,
|
||||
jp: `${keyName().jp}なし`
|
||||
},
|
||||
series: props.series,
|
||||
slot: props.slot,
|
||||
group: -1,
|
||||
order: 0
|
||||
}
|
||||
|
||||
const organizeWeaponKeys = useCallback((weaponKeys: WeaponKey[]) => {
|
||||
const numGroups = Math.max.apply(Math, weaponKeys.map(key => key.group))
|
||||
let groupedKeys = []
|
||||
for (let i = 0; i <= numGroups; i++) {
|
||||
groupedKeys[i] = weaponKeys.filter(key => key.group == i)
|
||||
}
|
||||
|
||||
setKeys(groupedKeys)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const filterParams = {
|
||||
params: {
|
||||
series: props.series,
|
||||
slot: props.slot
|
||||
}
|
||||
}
|
||||
|
||||
function fetchWeaponKeys() {
|
||||
api.endpoints.weapon_keys.getAll(filterParams)
|
||||
.then((response) => {
|
||||
const keys = response.data.map((k: any) => k.weapon_key)
|
||||
organizeWeaponKeys(keys)
|
||||
})
|
||||
}
|
||||
|
||||
fetchWeaponKeys()
|
||||
}, [props.series, props.slot, organizeWeaponKeys])
|
||||
|
||||
function weaponKeyGroup(index: number) {
|
||||
['α','β','γ','Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
||||
|
||||
const sortByOrder = (a: WeaponKey, b: WeaponKey) => a.order > b.order ? 1 : -1
|
||||
|
||||
const options = keys && keys.length > 0 && keys[index].length > 0 &&
|
||||
keys[index].sort(sortByOrder).map((item, i) => {
|
||||
return (
|
||||
<option key={i} value={item.id}>{item.name.en}</option>
|
||||
)
|
||||
})
|
||||
|
||||
let name: { [key: string]: string } = {}
|
||||
if (props.series == 2 && index == 0)
|
||||
name = pendulumNames[0]
|
||||
else if (props.series == 2 && props.slot == 1 && index == 1)
|
||||
name = pendulumNames[1]
|
||||
else if (props.series == 3)
|
||||
name = telumaNames[index]
|
||||
else if (props.series == 17)
|
||||
name = gauphNames[props.slot]
|
||||
else if (props.series == 22)
|
||||
name = emblemNames[index]
|
||||
|
||||
return (
|
||||
<optgroup key={index} label={ (props.series == 17 && props.slot == 2) ? name.en : `${name.en}s`}>
|
||||
{options}
|
||||
</optgroup>
|
||||
)
|
||||
}
|
||||
|
||||
const emptyOption = () => {
|
||||
let name = ''
|
||||
if (props.series == 2)
|
||||
name = pendulumNames[0].en
|
||||
else if (props.series == 3)
|
||||
name = telumaNames[0].en
|
||||
else if (props.series == 17)
|
||||
name = gauphNames[props.slot].en
|
||||
else if (props.series == 22)
|
||||
name = emblemNames[0].en
|
||||
|
||||
return `No ${name}`
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
key={ (props.currentValue) ? props.currentValue.id : ''}
|
||||
defaultValue={ (props.currentValue) ? props.currentValue.id : '' }
|
||||
onBlur={props.onBlur}
|
||||
onChange={props.onChange}
|
||||
ref={ref}>
|
||||
<option key="-1" value="-1">{emptyOption()}</option>
|
||||
{ Array.from(Array(keys?.length)).map((x, i) => {
|
||||
return weaponKeyGroup(i)
|
||||
})}
|
||||
</select>
|
||||
)
|
||||
})
|
||||
|
||||
export default WeaponKeyDropdown
|
||||
44
components/WeaponModal/index.scss
Normal file
44
components/WeaponModal/index.scss
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
.Weapon.Dialog {
|
||||
.mods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * 4;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit / 2;
|
||||
|
||||
h3 {
|
||||
color: $grey-50;
|
||||
font-size: $font-small;
|
||||
margin-bottom: $unit;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: $grey-90;
|
||||
}
|
||||
}
|
||||
|
||||
.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-40;
|
||||
|
||||
&:hover {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
216
components/WeaponModal/index.tsx
Normal file
216
components/WeaponModal/index.tsx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { AxiosResponse } from 'axios'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
|
||||
import AXSelect from '~components/AxSelect'
|
||||
import ElementToggle from '~components/ElementToggle'
|
||||
import WeaponKeyDropdown from '~components/WeaponKeyDropdown'
|
||||
import Button from '~components/Button'
|
||||
|
||||
import api from '~utils/api'
|
||||
import { appState } from '~utils/appState'
|
||||
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import './index.scss'
|
||||
|
||||
|
||||
interface GridWeaponObject {
|
||||
weapon: {
|
||||
element?: number
|
||||
weapon_key1_id?: string
|
||||
weapon_key2_id?: string
|
||||
weapon_key3_id?: string
|
||||
ax_modifier1?: number
|
||||
ax_modifier2?: number
|
||||
ax_strength1?: number
|
||||
ax_strength2?: number
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
gridWeapon: GridWeapon
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const WeaponModal = (props: Props) => {
|
||||
// Cookies
|
||||
const [cookies, _] = useCookies(['user'])
|
||||
const headers = (cookies.user != null) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.user.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Refs
|
||||
const weaponKey1Select = React.createRef<HTMLSelectElement>()
|
||||
const weaponKey2Select = React.createRef<HTMLSelectElement>()
|
||||
const weaponKey3Select = React.createRef<HTMLSelectElement>()
|
||||
|
||||
// State
|
||||
const [open, setOpen] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
const [element, setElement] = useState(-1)
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||
|
||||
function receiveAxValues(primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) {
|
||||
setPrimaryAxModifier(primaryAxModifier)
|
||||
setSecondaryAxModifier(secondaryAxModifier)
|
||||
|
||||
setPrimaryAxValue(primaryAxValue)
|
||||
setSecondaryAxValue(secondaryAxValue)
|
||||
}
|
||||
|
||||
function receiveAxValidity(isValid: boolean) {
|
||||
setFormValid(isValid)
|
||||
}
|
||||
|
||||
function receiveElementValue(element: string) {
|
||||
setElement(parseInt(element))
|
||||
}
|
||||
|
||||
function prepareObject() {
|
||||
let object: GridWeaponObject = { weapon: {} }
|
||||
|
||||
if (props.gridWeapon.object.element == 0)
|
||||
object.weapon.element = element
|
||||
|
||||
if ([2, 3, 17, 24].includes(props.gridWeapon.object.series))
|
||||
object.weapon.weapon_key1_id = weaponKey1Select.current?.value
|
||||
|
||||
if ([2, 3, 17].includes(props.gridWeapon.object.series))
|
||||
object.weapon.weapon_key2_id = weaponKey2Select.current?.value
|
||||
|
||||
if (props.gridWeapon.object.series == 17)
|
||||
object.weapon.weapon_key3_id = weaponKey3Select.current?.value
|
||||
|
||||
if (props.gridWeapon.object.ax > 0) {
|
||||
object.weapon.ax_modifier1 = primaryAxModifier
|
||||
object.weapon.ax_modifier2 = secondaryAxModifier
|
||||
object.weapon.ax_strength1 = primaryAxValue
|
||||
object.weapon.ax_strength2 = secondaryAxValue
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
async function updateWeapon() {
|
||||
const updateObject = prepareObject()
|
||||
return await api.endpoints.grid_weapons.update(props.gridWeapon.id, updateObject, headers)
|
||||
.then(response => processResult(response))
|
||||
.catch(error => processError(error))
|
||||
}
|
||||
|
||||
function processResult(response: AxiosResponse) {
|
||||
const gridWeapon: GridWeapon = response.data.grid_weapon
|
||||
|
||||
if (gridWeapon.mainhand)
|
||||
appState.grid.weapons.mainWeapon = gridWeapon
|
||||
else
|
||||
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
||||
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function processError(error: any) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
const elementSelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>Element</h3>
|
||||
<ElementToggle
|
||||
currentElement={props.gridWeapon.element}
|
||||
sendValue={receiveElementValue}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const keySelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>Weapon Keys</h3>
|
||||
{ ([2, 3, 17, 22].includes(props.gridWeapon.object.series)) ?
|
||||
<WeaponKeyDropdown
|
||||
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[0] : undefined }
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={0}
|
||||
ref={weaponKey1Select} />
|
||||
: ''}
|
||||
|
||||
{ ([2, 3, 17].includes(props.gridWeapon.object.series)) ?
|
||||
<WeaponKeyDropdown
|
||||
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[1] : undefined }
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={1}
|
||||
ref={weaponKey2Select} />
|
||||
: ''}
|
||||
|
||||
{ (props.gridWeapon.object.series == 17) ?
|
||||
<WeaponKeyDropdown
|
||||
currentValue={ (props.gridWeapon.weapon_keys) ? props.gridWeapon.weapon_keys[2] : undefined }
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={2}
|
||||
ref={weaponKey3Select} />
|
||||
: ''}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const axSelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<AXSelect
|
||||
axType={props.gridWeapon.object.ax}
|
||||
currentSkills={props.gridWeapon.ax}
|
||||
sendValidity={receiveAxValidity}
|
||||
sendValues={receiveAxValues}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function openChange(open: boolean) {
|
||||
setFormValid(false)
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||
<Dialog.Trigger asChild>
|
||||
{ props.children }
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content className="Weapon Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTop">
|
||||
<Dialog.Title className="SubTitle">Modify Weapon</Dialog.Title>
|
||||
<Dialog.Title className="DialogTitle">{props.gridWeapon.object.name.en}</Dialog.Title>
|
||||
</div>
|
||||
<Dialog.Close className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
</span>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
|
||||
<div className="mods">
|
||||
{ (props.gridWeapon.object.element == 0) ? elementSelect() : '' }
|
||||
{ ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) ? keySelect() : '' }
|
||||
{ (props.gridWeapon.object.ax > 0) ? axSelect() : '' }
|
||||
<Button click={updateWeapon} disabled={!formValid}>Save Weapon</Button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default WeaponModal
|
||||
|
|
@ -3,11 +3,16 @@
|
|||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-height: 139px;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
&:hover .Button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.editable .WeaponImage:hover {
|
||||
border: $hover-stroke;
|
||||
box-shadow: $hover-shadow;
|
||||
|
|
@ -66,6 +71,16 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.Button {
|
||||
background: white;
|
||||
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.14);
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: $unit;
|
||||
top: $unit;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: $grey-00;
|
||||
font-size: $font-button;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ import React, { useEffect, useState } from 'react'
|
|||
import classnames from 'classnames'
|
||||
|
||||
import SearchModal from '~components/SearchModal'
|
||||
import WeaponModal from '~components/WeaponModal'
|
||||
import UncapIndicator from '~components/UncapIndicator'
|
||||
import PlusIcon from '~public/icons/Add.svg'
|
||||
import Button from '~components/Button'
|
||||
|
||||
import { ButtonType } from '~utils/enums'
|
||||
|
||||
import PlusIcon from '~public/icons/Add.svg'
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
|
|
@ -39,10 +43,17 @@ const WeaponUnit = (props: Props) => {
|
|||
if (props.gridWeapon) {
|
||||
const weapon = props.gridWeapon.object!
|
||||
|
||||
if (props.unitType == 0)
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
||||
else
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
||||
if (props.unitType == 0) {
|
||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
||||
else
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
||||
} else {
|
||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
||||
else
|
||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
||||
}
|
||||
}
|
||||
|
||||
setImageUrl(imgSrc)
|
||||
|
|
@ -53,6 +64,13 @@ const WeaponUnit = (props: Props) => {
|
|||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
||||
}
|
||||
|
||||
function canBeModified(gridWeapon: GridWeapon) {
|
||||
const weapon = gridWeapon.object
|
||||
|
||||
return weapon.ax > 0 ||
|
||||
(weapon.series) && [2, 3, 17, 22].includes(weapon.series)
|
||||
}
|
||||
|
||||
const image = (
|
||||
<div className="WeaponImage">
|
||||
<img alt={weapon?.name.en} className="grid_image" src={imageUrl} />
|
||||
|
|
@ -61,18 +79,26 @@ const WeaponUnit = (props: Props) => {
|
|||
)
|
||||
|
||||
const editableImage = (
|
||||
<SearchModal
|
||||
placeholderText="Search for a weapon..."
|
||||
fromPosition={props.position}
|
||||
object="weapons"
|
||||
send={props.updateObject}>
|
||||
{image}
|
||||
</SearchModal>
|
||||
<div className="WeaponImage">
|
||||
<SearchModal
|
||||
placeholderText="Search for a weapon..."
|
||||
fromPosition={props.position}
|
||||
object="weapons"
|
||||
send={props.updateObject}>
|
||||
{image}
|
||||
</SearchModal>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes}>
|
||||
{ (props.editable && gridWeapon && canBeModified(gridWeapon)) ?
|
||||
<WeaponModal gridWeapon={gridWeapon}>
|
||||
<div>
|
||||
<Button icon="settings" type={ButtonType.IconOnly}/>
|
||||
</div>
|
||||
</WeaponModal>: '' }
|
||||
{ (props.editable) ? editableImage : image }
|
||||
{ (gridWeapon) ?
|
||||
<UncapIndicator
|
||||
|
|
|
|||
186
package-lock.json
generated
186
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@radix-ui/react-hover-card": "^0.1.3",
|
||||
"@radix-ui/react-label": "^0.1.4",
|
||||
"@radix-ui/react-switch": "^0.1.4",
|
||||
"@radix-ui/react-toggle-group": "^0.1.5",
|
||||
"@svgr/webpack": "^6.2.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"axios": "^0.25.0",
|
||||
|
|
@ -2630,6 +2631,107 @@
|
|||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-0.1.4.tgz",
|
||||
"integrity": "sha512-gxUq6NgMc4ChV8VJnwdYqueeoblspwXHAexYo+jM9N2hFLbI1C587jLjdTHzIcUa9q68Xaw4jtiImWDOokEhRw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.5.tgz",
|
||||
"integrity": "sha512-Yp14wFiqe00azF+sG5CCJz4JGOP/f5Jj+CxLlZCmMpG5qhVTWeaeG4YH6pvX4KL41fS8x9FAaLb8wW9y01o67g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-roving-focus": "0.1.5",
|
||||
"@radix-ui/react-toggle": "0.1.4",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-collection": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-0.1.4.tgz",
|
||||
"integrity": "sha512-3muGI15IdgaDFjOcO7xX8a35HQRBRF6LH9pS6UCeZeRmbslkVeHyJRQr2rzICBUoX7zgIA0kXyMDbpQnJGyJTA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-id": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.5.tgz",
|
||||
"integrity": "sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz",
|
||||
"integrity": "sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz",
|
||||
"integrity": "sha512-ClwKPS5JZE+PaHCoW7eu1onvE61pDv4kO8W4t5Ra3qMFQiTJLZMdpBQUhksN//DaVygoLirz4Samdr5Y1x1FSA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-collection": "0.1.4",
|
||||
"@radix-ui/react-compose-refs": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-id": "0.1.5",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-use-callback-ref": "0.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz",
|
||||
"integrity": "sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-body-pointer-events": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.0.tgz",
|
||||
|
|
@ -8660,6 +8762,90 @@
|
|||
"@radix-ui/react-use-size": "0.1.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-toggle": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-0.1.4.tgz",
|
||||
"integrity": "sha512-gxUq6NgMc4ChV8VJnwdYqueeoblspwXHAexYo+jM9N2hFLbI1C587jLjdTHzIcUa9q68Xaw4jtiImWDOokEhRw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz",
|
||||
"integrity": "sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-toggle-group": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.5.tgz",
|
||||
"integrity": "sha512-Yp14wFiqe00azF+sG5CCJz4JGOP/f5Jj+CxLlZCmMpG5qhVTWeaeG4YH6pvX4KL41fS8x9FAaLb8wW9y01o67g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-roving-focus": "0.1.5",
|
||||
"@radix-ui/react-toggle": "0.1.4",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-collection": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-0.1.4.tgz",
|
||||
"integrity": "sha512-3muGI15IdgaDFjOcO7xX8a35HQRBRF6LH9pS6UCeZeRmbslkVeHyJRQr2rzICBUoX7zgIA0kXyMDbpQnJGyJTA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-id": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.5.tgz",
|
||||
"integrity": "sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "0.1.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz",
|
||||
"integrity": "sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "0.1.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-roving-focus": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz",
|
||||
"integrity": "sha512-ClwKPS5JZE+PaHCoW7eu1onvE61pDv4kO8W4t5Ra3qMFQiTJLZMdpBQUhksN//DaVygoLirz4Samdr5Y1x1FSA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "0.1.0",
|
||||
"@radix-ui/react-collection": "0.1.4",
|
||||
"@radix-ui/react-compose-refs": "0.1.0",
|
||||
"@radix-ui/react-context": "0.1.1",
|
||||
"@radix-ui/react-id": "0.1.5",
|
||||
"@radix-ui/react-primitive": "0.1.4",
|
||||
"@radix-ui/react-use-callback-ref": "0.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-body-pointer-events": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"@radix-ui/react-hover-card": "^0.1.3",
|
||||
"@radix-ui/react-label": "^0.1.4",
|
||||
"@radix-ui/react-switch": "^0.1.4",
|
||||
"@radix-ui/react-toggle-group": "^0.1.5",
|
||||
"@svgr/webpack": "^6.2.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"axios": "^0.25.0",
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
type Data = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
<svg viewBox="0 0 14 14" fill="#444" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3121 4.60937C2.09636 4.43701 1.78173 4.47219 1.60937 4.68793C1.43701 4.90368 1.47219 5.2183 1.68793 5.39066L6.68107 9.37966C6.75234 9.4366 6.8344 9.47089 6.91856 9.48351C7.05774 9.51055 7.20746 9.47854 7.32678 9.38296L12.311 5.39025C12.5265 5.21761 12.5613 4.90294 12.3886 4.68742C12.216 4.4719 11.9013 4.43714 11.6858 4.60979L7.00557 8.35897L2.3121 4.60937Z" />
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.93618 4.62991C2.73179 4.44423 2.41557 4.4594 2.22989 4.6638C2.04421 4.8682 2.05938 5.18441 2.26378 5.37009L6.65743 9.36145C6.72962 9.42702 6.81576 9.46755 6.90516 9.48353C7.05808 9.51688 7.2243 9.47812 7.34882 9.3647L11.7346 5.36964C11.9388 5.18368 11.9535 4.86745 11.7676 4.6633C11.5816 4.45916 11.2654 4.44441 11.0612 4.63037L7.00447 8.32569L2.93618 4.62991Z" fill="black"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 532 B |
3
public/icons/ArrowDark.svg
Normal file
3
public/icons/ArrowDark.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.9363 5.12991C2.73191 4.94423 2.41569 4.9594 2.23001 5.1638C2.04433 5.3682 2.0595 5.68441 2.2639 5.87009L6.65755 9.86145C6.72974 9.92702 6.81588 9.96755 6.90529 9.98353C7.05821 10.0169 7.22443 9.97812 7.34894 9.8647L11.7348 5.86964C11.9389 5.68368 11.9537 5.36745 11.7677 5.1633C11.5817 4.95916 11.2655 4.94441 11.0614 5.13037L7.00459 8.82569L2.9363 5.12991Z" fill="#888888"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 531 B |
6
public/icons/Settings.svg
Normal file
6
public/icons/Settings.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -49,7 +49,7 @@ select {
|
|||
background-image: url('/icons/Arrow.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: 98%;
|
||||
background-position-x: 97%;
|
||||
background-size: $unit * 1.5;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
|
|
@ -110,6 +110,7 @@ select {
|
|||
|
||||
.DialogHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $unit;
|
||||
|
||||
.left {
|
||||
|
|
@ -150,6 +151,19 @@ select {
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.DialogTop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: $unit / 2;
|
||||
|
||||
.SubTitle {
|
||||
color: $grey-50;
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
}
|
||||
}
|
||||
|
||||
.DialogDescription {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
|||
11
types/AxSkill.d.ts
vendored
Normal file
11
types/AxSkill.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
interface AxSkill {
|
||||
name: {
|
||||
en: string,
|
||||
jp: string
|
||||
},
|
||||
id: number,
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
suffix?: string,
|
||||
secondary?: AxSkill[]
|
||||
}
|
||||
3
types/GridWeapon.d.ts
vendored
3
types/GridWeapon.d.ts
vendored
|
|
@ -4,4 +4,7 @@ interface GridWeapon {
|
|||
position: number
|
||||
object: Weapon
|
||||
uncap_level: number
|
||||
element: number
|
||||
weapon_keys?: Array<WeaponKey>
|
||||
ax?: Array<SimpleAxSkill>
|
||||
}
|
||||
4
types/SimpleAxSkill.d.ts
vendored
Normal file
4
types/SimpleAxSkill.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
interface SimpleAxSkill {
|
||||
modifier: number
|
||||
strength: number
|
||||
}
|
||||
2
types/Weapon.d.ts
vendored
2
types/Weapon.d.ts
vendored
|
|
@ -7,6 +7,8 @@ interface Weapon {
|
|||
proficiency: number
|
||||
max_level: number
|
||||
max_skill_level: number
|
||||
series: number
|
||||
ax: number
|
||||
name: {
|
||||
en: string
|
||||
jp: string
|
||||
|
|
|
|||
11
types/WeaponKey.d.ts
vendored
Normal file
11
types/WeaponKey.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
interface WeaponKey {
|
||||
id: string
|
||||
name: {
|
||||
en: string,
|
||||
jp: string
|
||||
}
|
||||
series: integer
|
||||
slot: integer
|
||||
group: integer
|
||||
order: integer
|
||||
}
|
||||
|
|
@ -103,10 +103,12 @@ class Api {
|
|||
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: 'parties' })
|
||||
api.createEntity( { name: 'grid_weapons' })
|
||||
api.createEntity( { name: 'characters' })
|
||||
api.createEntity( { name: 'weapons' })
|
||||
api.createEntity( { name: 'summons' })
|
||||
api.createEntity( { name: 'raids' })
|
||||
api.createEntity( { name: 'weapon_keys' })
|
||||
api.createEntity( { name: 'favorites' })
|
||||
|
||||
export default api
|
||||
791
utils/axData.tsx
Normal file
791
utils/axData.tsx
Normal file
|
|
@ -0,0 +1,791 @@
|
|||
export const axData: AxSkill[][] = [
|
||||
[
|
||||
{
|
||||
name: {
|
||||
"en": "ATK",
|
||||
"jp": "攻撃"
|
||||
},
|
||||
id: 0,
|
||||
minValue: 1,
|
||||
maxValue: 3.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Double Attack Rate",
|
||||
"jp": "DA確率"
|
||||
},
|
||||
id: 5,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Triple Attack Rate",
|
||||
"jp": "TA確率"
|
||||
},
|
||||
id: 6,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Skill DMG Cap",
|
||||
"jp": "アビ上限"
|
||||
},
|
||||
id: 7,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "DEF",
|
||||
"jp": "防御"
|
||||
},
|
||||
id: 1,
|
||||
minValue: 1,
|
||||
maxValue: 8,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "HP",
|
||||
"jp": "HP"
|
||||
},
|
||||
id: 2,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Enmity",
|
||||
"jp": "背水"
|
||||
},
|
||||
id: 11,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "HP",
|
||||
"jp": "HP"
|
||||
},
|
||||
id: 2,
|
||||
minValue: 1,
|
||||
maxValue: 11,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "DEF",
|
||||
"jp": "防御"
|
||||
},
|
||||
id: 1,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 8.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "ATK",
|
||||
"jp": "攻撃"
|
||||
},
|
||||
id: 0,
|
||||
minValue: 1,
|
||||
maxValue: 1.5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental ATK",
|
||||
"jp": "全属性攻撃力"
|
||||
},
|
||||
id: 13,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG Cap",
|
||||
"jp": "奥義上限"
|
||||
},
|
||||
id: 8,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Multiattack Rate",
|
||||
"jp": "連撃率"
|
||||
},
|
||||
id: 4,
|
||||
minValue: 1,
|
||||
maxValue: 4,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental ATK",
|
||||
"jp": "全属性攻撃力"
|
||||
},
|
||||
id: 13,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Double Attack Rate",
|
||||
"jp": "DA確率"
|
||||
},
|
||||
id: 5,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Triple Attack Rate",
|
||||
"jp": "TA確率"
|
||||
},
|
||||
id: 6,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
}
|
||||
]
|
||||
}
|
||||
], [
|
||||
{
|
||||
name: {
|
||||
"en": "ATK",
|
||||
"jp": "攻撃"
|
||||
},
|
||||
id: 0,
|
||||
minValue: 1,
|
||||
maxValue: 3.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 8.5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Multiattack Rate",
|
||||
"jp": "連撃確率"
|
||||
},
|
||||
id: 4,
|
||||
minValue: 1.5,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Normal ATK DMG Cap",
|
||||
"jp": "通常ダメ上限"
|
||||
},
|
||||
id: 14,
|
||||
minValue: 0.5,
|
||||
maxValue: 1.5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Supplemental Skill DMG",
|
||||
"jp": "アビ与ダメ上昇"
|
||||
},
|
||||
id: 15,
|
||||
minValue: 1,
|
||||
maxValue: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "DEF",
|
||||
"jp": "防御"
|
||||
},
|
||||
id: 1,
|
||||
minValue: 1,
|
||||
maxValue: 8,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental DMG Reduction",
|
||||
"jp": "属性ダメ軽減"
|
||||
},
|
||||
id: 17,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Enmity",
|
||||
"jp": "背水"
|
||||
},
|
||||
id: 11,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "HP",
|
||||
"jp": "HP"
|
||||
},
|
||||
id: 2,
|
||||
minValue: 1,
|
||||
maxValue: 11,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental DMG Reduction",
|
||||
"jp": "属性ダメ軽減"
|
||||
},
|
||||
id: 17,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 8.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "Multiattack Rate",
|
||||
"jp": "連撃率"
|
||||
},
|
||||
id: 4,
|
||||
minValue: 1.5,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Supplemental Skill DMG",
|
||||
"jp": "アビ与ダメ上昇"
|
||||
},
|
||||
id: 15,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Supplemental C.A. DMG",
|
||||
"jp": "奥義与ダメ上昇"
|
||||
},
|
||||
id: 16,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Multiattack Rate",
|
||||
"jp": "連撃率"
|
||||
},
|
||||
id: 4,
|
||||
minValue: 1,
|
||||
maxValue: 4,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "Supplemental C.A. DMG",
|
||||
"jp": "奥義与ダメ上昇"
|
||||
},
|
||||
id: 16,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Normal ATK DMG Cap",
|
||||
"jp": "通常ダメ上限"
|
||||
},
|
||||
id: 14,
|
||||
minValue: 0.5,
|
||||
maxValue: 1.5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Enmity",
|
||||
"jp": "背水"
|
||||
},
|
||||
id: 11,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
}
|
||||
]
|
||||
}
|
||||
], [
|
||||
{
|
||||
name: {
|
||||
"en": "ATK",
|
||||
"jp": "攻撃"
|
||||
},
|
||||
id: 0,
|
||||
minValue: 1,
|
||||
maxValue: 3.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Double Attack Rate",
|
||||
"jp": "DA確率"
|
||||
},
|
||||
id: 5,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Triple Attack Rate",
|
||||
"jp": "TA確率"
|
||||
},
|
||||
id: 6,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Skill DMG Cap",
|
||||
"jp": "アビ上限"
|
||||
},
|
||||
id: 7,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "DEF",
|
||||
"jp": "防御"
|
||||
},
|
||||
id: 1,
|
||||
minValue: 1,
|
||||
maxValue: 8,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "HP",
|
||||
"jp": "HP"
|
||||
},
|
||||
id: 2,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Enmity",
|
||||
"jp": "背水"
|
||||
},
|
||||
id: 11,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "HP",
|
||||
"jp": "HP"
|
||||
},
|
||||
id: 2,
|
||||
minValue: 1,
|
||||
maxValue: 11,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "DEF",
|
||||
"jp": "防御"
|
||||
},
|
||||
id: 1,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Debuff Resistance",
|
||||
"jp": "弱体耐性"
|
||||
},
|
||||
id: 9,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Healing",
|
||||
"jp": "回復性能"
|
||||
},
|
||||
id: 10,
|
||||
minValue: 2,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 8.5,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "ATK",
|
||||
"jp": "攻撃"
|
||||
},
|
||||
id: 0,
|
||||
minValue: 1,
|
||||
maxValue: 1.5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental ATK",
|
||||
"jp": "全属性攻撃力"
|
||||
},
|
||||
id: 13,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG Cap",
|
||||
"jp": "奥義上限"
|
||||
},
|
||||
id: 8,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Stamina",
|
||||
"jp": "渾身"
|
||||
},
|
||||
id: 12,
|
||||
minValue: 1,
|
||||
maxValue: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Multiattack Rate",
|
||||
"jp": "連撃率"
|
||||
},
|
||||
id: 4,
|
||||
minValue: 1,
|
||||
maxValue: 4,
|
||||
suffix: '%',
|
||||
secondary: [
|
||||
{
|
||||
name: {
|
||||
"en": "C.A. DMG",
|
||||
"jp": "奥義ダメ"
|
||||
},
|
||||
id: 3,
|
||||
minValue: 2,
|
||||
maxValue: 4,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Elemental ATK",
|
||||
"jp": "全属性攻撃力"
|
||||
},
|
||||
id: 13,
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Double Attack Rate",
|
||||
"jp": "DA確率"
|
||||
},
|
||||
id: 5,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Triple Attack Rate",
|
||||
"jp": "TA確率"
|
||||
},
|
||||
id: 6,
|
||||
minValue: 1,
|
||||
maxValue: 2,
|
||||
suffix: '%'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "EXP Up",
|
||||
"jp": "EXP UP"
|
||||
},
|
||||
id: 18,
|
||||
minValue: 5,
|
||||
maxValue: 10,
|
||||
suffix: '%'
|
||||
},
|
||||
{
|
||||
name: {
|
||||
"en": "Rupies",
|
||||
"jp": "獲得ルピ"
|
||||
},
|
||||
id: 19,
|
||||
minValue: 10,
|
||||
maxValue: 20,
|
||||
suffix: '%'
|
||||
}
|
||||
]
|
||||
]
|
||||
Loading…
Reference in a new issue