diff --git a/components/AxSelect/index.scss b/components/AxSelect/index.scss new file mode 100644 index 00000000..559645d3 --- /dev/null +++ b/components/AxSelect/index.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/components/AxSelect/index.tsx b/components/AxSelect/index.tsx new file mode 100644 index 00000000..636261ba --- /dev/null +++ b/components/AxSelect/index.tsx @@ -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({ + 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() + const primaryAxValueInput = React.createRef() + const secondaryAxModifierSelect = React.createRef() + const secondaryAxValueInput = React.createRef() + + // 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 ( + + ) + }) + } 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 ( + + ) + }) + } + } + } + + axOptionElements?.unshift() + return axOptionElements + } + + function handleSelectChange(event: React.ChangeEvent) { + 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) { + 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 ( +
+
+
+ + +
+

{errors.axValue1}

+
+ +
+
+ + +
+

{errors.axValue2}

+
+
+ ) +} + +export default AXSelect \ No newline at end of file diff --git a/components/Button/index.scss b/components/Button/index.scss index 616c9cf0..4f363ce5 100644 --- a/components/Button/index.scss +++ b/components/Button/index.scss @@ -86,6 +86,11 @@ fill: none; stroke: $grey-50; } + + &.settings svg { + height: 13px; + width: 13px; + } } &.Active { diff --git a/components/Button/index.tsx b/components/Button/index.tsx index 3fcd68f5..4d670555 100644 --- a/components/Button/index.tsx +++ b/components/Button/index.tsx @@ -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 { icon = + } else if (this.props.icon === 'settings') { + icon = + + } const classes = classNames({ diff --git a/components/ElementToggle/index.scss b/components/ElementToggle/index.scss new file mode 100644 index 00000000..c97a1d19 --- /dev/null +++ b/components/ElementToggle/index.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/components/ElementToggle/index.tsx b/components/ElementToggle/index.tsx new file mode 100644 index 00000000..68486bdb --- /dev/null +++ b/components/ElementToggle/index.tsx @@ -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 ( + + + Null + + + Wind + + + Fire + + + Water + + + Earth + + + Dark + + + Light + + + ) +} + +export default ElementToggle \ No newline at end of file diff --git a/components/FilterBar/index.scss b/components/FilterBar/index.scss index fd78e099..ba8cdf4b 100644 --- a/components/FilterBar/index.scss +++ b/components/FilterBar/index.scss @@ -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; diff --git a/components/PartySegmentedControl/index.tsx b/components/PartySegmentedControl/index.tsx index 18cc6c55..97ca5895 100644 --- a/components/PartySegmentedControl/index.tsx +++ b/components/PartySegmentedControl/index.tsx @@ -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 diff --git a/components/RaidDropdown/index.tsx b/components/RaidDropdown/index.tsx index 208af245..47ad6b26 100644 --- a/components/RaidDropdown/index.tsx +++ b/components/RaidDropdown/index.tsx @@ -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(function useFieldSet(props, ref) { - const [cookies] = useCookies(['user']) const [raids, setRaids] = useState() const raidGroups = [ @@ -50,12 +48,8 @@ const RaidDropdown = React.forwardRef(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(function useFiel } fetchRaids() - }, [cookies.user, organizeRaids]) + }, [organizeRaids]) function raidGroup(index: number) { const options = raids && raids.length > 0 && raids[index].length > 0 && diff --git a/components/WeaponGrid/index.tsx b/components/WeaponGrid/index.tsx index 753cc288..efa1311a 100644 --- a/components/WeaponGrid/index.tsx +++ b/components/WeaponGrid/index.tsx @@ -232,7 +232,7 @@ const WeaponGrid = (props: Props) => { // Render: JSX components const mainhandElement = ( { return (
  • { const extraGridElement = ( ) => void + onBlur?: (event: React.ChangeEvent) => void +} + +const WeaponKeyDropdown = React.forwardRef(function useFieldSet(props, ref) { + const [keys, setKeys] = useState([]) + + 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 ( + + ) + }) + + 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 ( + + {options} + + ) + } + + 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 ( + + ) +}) + +export default WeaponKeyDropdown diff --git a/components/WeaponModal/index.scss b/components/WeaponModal/index.scss new file mode 100644 index 00000000..a0ec1acd --- /dev/null +++ b/components/WeaponModal/index.scss @@ -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; + } + } + } + } +} \ No newline at end of file diff --git a/components/WeaponModal/index.tsx b/components/WeaponModal/index.tsx new file mode 100644 index 00000000..eba61367 --- /dev/null +++ b/components/WeaponModal/index.tsx @@ -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() + const weaponKey2Select = React.createRef() + const weaponKey3Select = React.createRef() + + // 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 ( +
    +

    Element

    + +
    + ) + } + + const keySelect = () => { + return ( +
    +

    Weapon Keys

    + { ([2, 3, 17, 22].includes(props.gridWeapon.object.series)) ? + + : ''} + + { ([2, 3, 17].includes(props.gridWeapon.object.series)) ? + + : ''} + + { (props.gridWeapon.object.series == 17) ? + + : ''} +
    + ) + } + + const axSelect = () => { + return ( +
    + +
    + ) + } + + function openChange(open: boolean) { + setFormValid(false) + setOpen(open) + } + + return ( + + + { props.children } + + + event.preventDefault() }> +
    +
    + Modify Weapon + {props.gridWeapon.object.name.en} +
    + + + + + +
    + +
    + { (props.gridWeapon.object.element == 0) ? elementSelect() : '' } + { ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) ? keySelect() : '' } + { (props.gridWeapon.object.ax > 0) ? axSelect() : '' } + +
    +
    + +
    +
    + ) +} + +export default WeaponModal \ No newline at end of file diff --git a/components/WeaponUnit/index.scss b/components/WeaponUnit/index.scss index e8bb254d..ccda269a 100644 --- a/components/WeaponUnit/index.scss +++ b/components/WeaponUnit/index.scss @@ -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; diff --git a/components/WeaponUnit/index.tsx b/components/WeaponUnit/index.tsx index f8541653..21df410e 100644 --- a/components/WeaponUnit/index.tsx +++ b/components/WeaponUnit/index.tsx @@ -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 = (
    {weapon?.name.en} @@ -61,18 +79,26 @@ const WeaponUnit = (props: Props) => { ) const editableImage = ( - - {image} - +
    + + {image} + +
    ) return (
    + { (props.editable && gridWeapon && canBeModified(gridWeapon)) ? + +
    +
    +
    : '' } { (props.editable) ? editableImage : image } { (gridWeapon) ? -) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/public/icons/Arrow.svg b/public/icons/Arrow.svg index 61369665..44d30bc4 100644 --- a/public/icons/Arrow.svg +++ b/public/icons/Arrow.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/ArrowDark.svg b/public/icons/ArrowDark.svg new file mode 100644 index 00000000..156139cb --- /dev/null +++ b/public/icons/ArrowDark.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/Settings.svg b/public/icons/Settings.svg new file mode 100644 index 00000000..9e8741c4 --- /dev/null +++ b/public/icons/Settings.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/styles/globals.scss b/styles/globals.scss index b70a95db..4f26f3f7 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -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; } diff --git a/types/AxSkill.d.ts b/types/AxSkill.d.ts new file mode 100644 index 00000000..69d42b39 --- /dev/null +++ b/types/AxSkill.d.ts @@ -0,0 +1,11 @@ +interface AxSkill { + name: { + en: string, + jp: string + }, + id: number, + minValue: number, + maxValue: number, + suffix?: string, + secondary?: AxSkill[] +} \ No newline at end of file diff --git a/types/GridWeapon.d.ts b/types/GridWeapon.d.ts index f2d7dd37..fe88cf27 100644 --- a/types/GridWeapon.d.ts +++ b/types/GridWeapon.d.ts @@ -4,4 +4,7 @@ interface GridWeapon { position: number object: Weapon uncap_level: number + element: number + weapon_keys?: Array + ax?: Array } \ No newline at end of file diff --git a/types/SimpleAxSkill.d.ts b/types/SimpleAxSkill.d.ts new file mode 100644 index 00000000..e13171e7 --- /dev/null +++ b/types/SimpleAxSkill.d.ts @@ -0,0 +1,4 @@ +interface SimpleAxSkill { + modifier: number + strength: number +} \ No newline at end of file diff --git a/types/Weapon.d.ts b/types/Weapon.d.ts index 0f3c1ead..d26caa7d 100644 --- a/types/Weapon.d.ts +++ b/types/Weapon.d.ts @@ -7,6 +7,8 @@ interface Weapon { proficiency: number max_level: number max_skill_level: number + series: number + ax: number name: { en: string jp: string diff --git a/types/WeaponKey.d.ts b/types/WeaponKey.d.ts new file mode 100644 index 00000000..d5541baa --- /dev/null +++ b/types/WeaponKey.d.ts @@ -0,0 +1,11 @@ +interface WeaponKey { + id: string + name: { + en: string, + jp: string + } + series: integer + slot: integer + group: integer + order: integer +} \ No newline at end of file diff --git a/utils/api.tsx b/utils/api.tsx index 61d49454..3781e275 100644 --- a/utils/api.tsx +++ b/utils/api.tsx @@ -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 \ No newline at end of file diff --git a/utils/axData.tsx b/utils/axData.tsx new file mode 100644 index 00000000..8ea007f4 --- /dev/null +++ b/utils/axData.tsx @@ -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: '%' + } + ] +] \ No newline at end of file