commit
a3ac29deb8
34 changed files with 1637 additions and 670 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -50,7 +50,7 @@ dist/
|
||||||
public/images/weapon*
|
public/images/weapon*
|
||||||
public/images/summon*
|
public/images/summon*
|
||||||
public/images/chara*
|
public/images/chara*
|
||||||
public/images/jobs
|
public/images/job*
|
||||||
|
|
||||||
# Typescript v1 declaration files
|
# Typescript v1 declaration files
|
||||||
typings/
|
typings/
|
||||||
|
|
|
||||||
52
components/Alert/index.scss
Normal file
52
components/Alert/index.scss
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
.AlertWrapper {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 21;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Alert {
|
||||||
|
background: white;
|
||||||
|
border-radius: $unit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
min-width: $unit * 20;
|
||||||
|
max-width: $unit * 40;
|
||||||
|
padding: $unit * 4;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: 1.26;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
font-size: $font-regular;
|
||||||
|
padding: ($unit * 1.5) ($unit * 2);
|
||||||
|
margin-top: $unit * 2;
|
||||||
|
|
||||||
|
&.btn-disabled {
|
||||||
|
background: $grey-90;
|
||||||
|
color: $grey-70;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.btn-disabled) {
|
||||||
|
background: $grey-90;
|
||||||
|
color: $grey-40;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $grey-80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
components/Alert/index.tsx
Normal file
51
components/Alert/index.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from "react"
|
||||||
|
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
import Button from "~components/Button"
|
||||||
|
import { ButtonType } from "~utils/enums"
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
title?: string
|
||||||
|
message: string
|
||||||
|
primaryAction?: () => void
|
||||||
|
primaryActionText?: string
|
||||||
|
cancelAction: () => void
|
||||||
|
cancelActionText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Alert = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<AlertDialog.Root open={props.open}>
|
||||||
|
<AlertDialog.Portal>
|
||||||
|
<AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} />
|
||||||
|
<div className="AlertWrapper">
|
||||||
|
<AlertDialog.Content className="Alert">
|
||||||
|
{props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ""}
|
||||||
|
<AlertDialog.Description className="description">
|
||||||
|
{props.message}
|
||||||
|
</AlertDialog.Description>
|
||||||
|
<div className="buttons">
|
||||||
|
<AlertDialog.Cancel asChild>
|
||||||
|
<Button onClick={props.cancelAction}>
|
||||||
|
{props.cancelActionText}
|
||||||
|
</Button>
|
||||||
|
</AlertDialog.Cancel>
|
||||||
|
{props.primaryAction ? (
|
||||||
|
<AlertDialog.Action onClick={props.primaryAction}>
|
||||||
|
{props.primaryActionText}
|
||||||
|
</AlertDialog.Action>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</div>
|
||||||
|
</AlertDialog.Portal>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Alert
|
||||||
|
|
@ -40,7 +40,6 @@ const CharacterConflictModal = (props: Props) => {
|
||||||
else if (uncap == 5) suffix = "03"
|
else if (uncap == 5) suffix = "03"
|
||||||
else if (uncap > 2) suffix = "02"
|
else if (uncap > 2) suffix = "02"
|
||||||
|
|
||||||
console.log(appState.grid.weapons.mainWeapon)
|
|
||||||
// Special casing for Lyria (and Young Cat eventually)
|
// Special casing for Lyria (and Young Cat eventually)
|
||||||
if (character?.granblue_id === "3030182000") {
|
if (character?.granblue_id === "3030182000") {
|
||||||
let element = 1
|
let element = 1
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,17 @@ import { useSnapshot } from "valtio"
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from "axios"
|
||||||
import debounce from "lodash.debounce"
|
import debounce from "lodash.debounce"
|
||||||
|
|
||||||
|
import Alert from "~components/Alert"
|
||||||
import JobSection from "~components/JobSection"
|
import JobSection from "~components/JobSection"
|
||||||
import CharacterUnit from "~components/CharacterUnit"
|
import CharacterUnit from "~components/CharacterUnit"
|
||||||
|
import CharacterConflictModal from "~components/CharacterConflictModal"
|
||||||
|
|
||||||
|
import type { JobSkillObject, SearchableObject } from "~types"
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import "./index.scss"
|
import "./index.scss"
|
||||||
import CharacterConflictModal from "~components/CharacterConflictModal"
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -46,6 +49,16 @@ const CharacterGrid = (props: Props) => {
|
||||||
const [conflicts, setConflicts] = useState<GridCharacter[]>([])
|
const [conflicts, setConflicts] = useState<GridCharacter[]>([])
|
||||||
const [position, setPosition] = useState(0)
|
const [position, setPosition] = useState(0)
|
||||||
|
|
||||||
|
// Set up state for data
|
||||||
|
const [job, setJob] = useState<Job | undefined>()
|
||||||
|
const [jobSkills, setJobSkills] = useState<JobSkillObject>({
|
||||||
|
0: undefined,
|
||||||
|
1: undefined,
|
||||||
|
2: undefined,
|
||||||
|
3: undefined,
|
||||||
|
})
|
||||||
|
const [errorMessage, setErrorMessage] = useState("")
|
||||||
|
|
||||||
// Create a temporary state to store previous character uncap values
|
// Create a temporary state to store previous character uncap values
|
||||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||||
[key: number]: number | undefined
|
[key: number]: number | undefined
|
||||||
|
|
@ -62,6 +75,11 @@ const CharacterGrid = (props: Props) => {
|
||||||
else appState.party.editable = false
|
else appState.party.editable = false
|
||||||
}, [props.new, accountData, party])
|
}, [props.new, accountData, party])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setJob(appState.party.job)
|
||||||
|
setJobSkills(appState.party.jobSkills)
|
||||||
|
}, [appState])
|
||||||
|
|
||||||
// Initialize an array of current uncap values for each characters
|
// Initialize an array of current uncap values for each characters
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let initialPreviousUncapValues: { [key: number]: number } = {}
|
let initialPreviousUncapValues: { [key: number]: number } = {}
|
||||||
|
|
@ -73,7 +91,7 @@ const CharacterGrid = (props: Props) => {
|
||||||
|
|
||||||
// Methods: Adding an object from search
|
// Methods: Adding an object from search
|
||||||
function receiveCharacterFromSearch(
|
function receiveCharacterFromSearch(
|
||||||
object: Character | Weapon | Summon,
|
object: SearchableObject,
|
||||||
position: number
|
position: number
|
||||||
) {
|
) {
|
||||||
const character = object as Character
|
const character = object as Character
|
||||||
|
|
@ -163,6 +181,69 @@ const CharacterGrid = (props: Props) => {
|
||||||
setIncoming(undefined)
|
setIncoming(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Methods: Saving job and job skills
|
||||||
|
const saveJob = function (job: Job) {
|
||||||
|
const payload = {
|
||||||
|
party: {
|
||||||
|
job_id: job ? job.id : "",
|
||||||
|
},
|
||||||
|
...headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (party.id && appState.party.editable) {
|
||||||
|
api.updateJob({ partyId: party.id, params: payload }).then((response) => {
|
||||||
|
const newParty = response.data.party
|
||||||
|
|
||||||
|
setJob(newParty.job)
|
||||||
|
appState.party.job = newParty.job
|
||||||
|
|
||||||
|
setJobSkills(newParty.job_skills)
|
||||||
|
appState.party.jobSkills = newParty.job_skills
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveJobSkill = function (skill: JobSkill, position: number) {
|
||||||
|
if (party.id && appState.party.editable) {
|
||||||
|
const positionedKey = `skill${position}_id`
|
||||||
|
|
||||||
|
let skillObject: {
|
||||||
|
[key: string]: string | undefined
|
||||||
|
skill0_id?: string
|
||||||
|
skill1_id?: string
|
||||||
|
skill2_id?: string
|
||||||
|
skill3_id?: string
|
||||||
|
} = {}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
party: skillObject,
|
||||||
|
...headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
skillObject[positionedKey] = skill.id
|
||||||
|
api
|
||||||
|
.updateJobSkills({ partyId: party.id, params: payload })
|
||||||
|
.then((response) => {
|
||||||
|
// Update the current skills
|
||||||
|
const newSkills = response.data.party.job_skills
|
||||||
|
setJobSkills(newSkills)
|
||||||
|
appState.party.jobSkills = newSkills
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const data = error.response.data
|
||||||
|
if (data.code == "too_many_skills_of_type") {
|
||||||
|
const message = `You can only add up to 2 ${
|
||||||
|
data.skill_type === "emp"
|
||||||
|
? data.skill_type.toUpperCase()
|
||||||
|
: data.skill_type
|
||||||
|
} skills to your party at once.`
|
||||||
|
setErrorMessage(message)
|
||||||
|
}
|
||||||
|
console.log(error.response.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Methods: Helpers
|
// Methods: Helpers
|
||||||
function characterUncapLevel(character: Character) {
|
function characterUncapLevel(character: Character) {
|
||||||
let uncapLevel
|
let uncapLevel
|
||||||
|
|
@ -250,11 +331,27 @@ const CharacterGrid = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelAlert() {
|
||||||
|
setErrorMessage("")
|
||||||
|
}
|
||||||
|
|
||||||
// Render: JSX components
|
// Render: JSX components
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Alert
|
||||||
|
open={errorMessage.length > 0}
|
||||||
|
message={errorMessage}
|
||||||
|
cancelAction={cancelAlert}
|
||||||
|
cancelActionText={"Got it"}
|
||||||
|
/>
|
||||||
<div id="CharacterGrid">
|
<div id="CharacterGrid">
|
||||||
<JobSection />
|
<JobSection
|
||||||
|
job={job}
|
||||||
|
jobSkills={jobSkills}
|
||||||
|
editable={party.editable}
|
||||||
|
saveJob={saveJob}
|
||||||
|
saveSkill={saveJobSkill}
|
||||||
|
/>
|
||||||
<CharacterConflictModal
|
<CharacterConflictModal
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
incomingCharacter={incoming}
|
incomingCharacter={incoming}
|
||||||
|
|
|
||||||
|
|
@ -1,130 +1,137 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from "react"
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from "next/router"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from "valtio"
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from "next-i18next"
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames"
|
||||||
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import CharacterHovercard from '~components/CharacterHovercard'
|
import CharacterHovercard from "~components/CharacterHovercard"
|
||||||
import SearchModal from '~components/SearchModal'
|
import SearchModal from "~components/SearchModal"
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from "~components/UncapIndicator"
|
||||||
import PlusIcon from '~public/icons/Add.svg'
|
import PlusIcon from "~public/icons/Add.svg"
|
||||||
|
|
||||||
import './index.scss'
|
import type { SearchableObject } from "~types"
|
||||||
import { getRedirectStatus } from 'next/dist/lib/load-custom-routes'
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridCharacter: GridCharacter | undefined
|
gridCharacter?: GridCharacter
|
||||||
position: number
|
position: number
|
||||||
editable: boolean
|
editable: boolean
|
||||||
updateObject: (object: Character | Weapon | Summon, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CharacterUnit = (props: Props) => {
|
const CharacterUnit = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
const { party, grid } = useSnapshot(appState)
|
const { party, grid } = useSnapshot(appState)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
|
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState("")
|
||||||
|
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
CharacterUnit: true,
|
CharacterUnit: true,
|
||||||
'editable': props.editable,
|
editable: props.editable,
|
||||||
'filled': (props.gridCharacter !== undefined)
|
filled: props.gridCharacter !== undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridCharacter = props.gridCharacter
|
const gridCharacter = props.gridCharacter
|
||||||
const character = gridCharacter?.object
|
const character = gridCharacter?.object
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateImageUrl()
|
generateImageUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ""
|
let imgSrc = ""
|
||||||
|
|
||||||
if (props.gridCharacter) {
|
|
||||||
const character = props.gridCharacter.object!
|
|
||||||
|
|
||||||
// Change the image based on the uncap level
|
if (props.gridCharacter) {
|
||||||
let suffix = '01'
|
const character = props.gridCharacter.object!
|
||||||
if (props.gridCharacter.uncap_level == 6)
|
|
||||||
suffix = '04'
|
|
||||||
else if (props.gridCharacter.uncap_level == 5)
|
|
||||||
suffix = '03'
|
|
||||||
else if (props.gridCharacter.uncap_level > 2)
|
|
||||||
suffix = '02'
|
|
||||||
|
|
||||||
// Special casing for Lyria (and Young Cat eventually)
|
// Change the image based on the uncap level
|
||||||
if (props.gridCharacter.object.granblue_id === '3030182000') {
|
let suffix = "01"
|
||||||
let element = 1
|
if (props.gridCharacter.uncap_level == 6) suffix = "04"
|
||||||
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
else if (props.gridCharacter.uncap_level == 5) suffix = "03"
|
||||||
element = grid.weapons.mainWeapon.element
|
else if (props.gridCharacter.uncap_level > 2) suffix = "02"
|
||||||
} else if (party.element != 0) {
|
|
||||||
element = party.element
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix = `${suffix}_0${element}`
|
// Special casing for Lyria (and Young Cat eventually)
|
||||||
}
|
if (props.gridCharacter.object.granblue_id === "3030182000") {
|
||||||
|
let element = 1
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg`
|
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
|
||||||
|
element = grid.weapons.mainWeapon.element
|
||||||
|
} else if (party.element != 0) {
|
||||||
|
element = party.element
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageUrl(imgSrc)
|
suffix = `${suffix}_0${element}`
|
||||||
|
}
|
||||||
|
|
||||||
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg`
|
||||||
}
|
}
|
||||||
|
|
||||||
function passUncapData(uncap: number) {
|
setImageUrl(imgSrc)
|
||||||
if (props.gridCharacter)
|
}
|
||||||
props.updateUncap(props.gridCharacter.id, props.position, uncap)
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = (
|
function passUncapData(uncap: number) {
|
||||||
<div className="CharacterImage">
|
if (props.gridCharacter)
|
||||||
<img alt={character?.name.en} className="grid_image" src={imageUrl} />
|
props.updateUncap(props.gridCharacter.id, props.position, uncap)
|
||||||
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' }
|
}
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const editableImage = (
|
const image = (
|
||||||
<SearchModal
|
<div className="CharacterImage">
|
||||||
placeholderText={t('search.placeholders.character')}
|
<img alt={character?.name.en} className="grid_image" src={imageUrl} />
|
||||||
fromPosition={props.position}
|
{props.editable ? (
|
||||||
object="characters"
|
<span className="icon">
|
||||||
send={props.updateObject}>
|
<PlusIcon />
|
||||||
{image}
|
</span>
|
||||||
</SearchModal>
|
) : (
|
||||||
)
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const unitContent = (
|
const editableImage = (
|
||||||
<div className={classes}>
|
<SearchModal
|
||||||
{ (props.editable) ? editableImage : image }
|
placeholderText={t("search.placeholders.character")}
|
||||||
{ (gridCharacter && character) ?
|
fromPosition={props.position}
|
||||||
<UncapIndicator
|
object="characters"
|
||||||
type="character"
|
send={props.updateObject}
|
||||||
flb={character.uncap.flb || false}
|
>
|
||||||
ulb={character.uncap.ulb || false}
|
{image}
|
||||||
uncapLevel={gridCharacter.uncap_level}
|
</SearchModal>
|
||||||
updateUncap={passUncapData}
|
)
|
||||||
special={character.special}
|
|
||||||
/> : '' }
|
|
||||||
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const withHovercard = (
|
const unitContent = (
|
||||||
<CharacterHovercard gridCharacter={gridCharacter!}>
|
<div className={classes}>
|
||||||
{unitContent}
|
{props.editable ? editableImage : image}
|
||||||
</CharacterHovercard>
|
{gridCharacter && character ? (
|
||||||
)
|
<UncapIndicator
|
||||||
|
type="character"
|
||||||
|
flb={character.uncap.flb || false}
|
||||||
|
ulb={character.uncap.ulb || false}
|
||||||
|
uncapLevel={gridCharacter.uncap_level}
|
||||||
|
updateUncap={passUncapData}
|
||||||
|
special={character.special}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<h3 className="CharacterName">{character?.name[locale]}</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
const withHovercard = (
|
||||||
(gridCharacter && !props.editable) ? withHovercard : unitContent
|
<CharacterHovercard gridCharacter={gridCharacter!}>
|
||||||
)
|
{unitContent}
|
||||||
|
</CharacterHovercard>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridCharacter && !props.editable ? withHovercard : unitContent
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterUnit
|
export default CharacterUnit
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,46 @@
|
||||||
import React from 'react'
|
import React from "react"
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from "next-i18next"
|
||||||
import SummonUnit from '~components/SummonUnit'
|
import SummonUnit from "~components/SummonUnit"
|
||||||
import './index.scss'
|
import { SearchableObject } from "~types"
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
grid: GridArray<GridSummon>
|
grid: GridArray<GridSummon>
|
||||||
editable: boolean
|
editable: boolean
|
||||||
exists: boolean
|
exists: boolean
|
||||||
found?: boolean
|
found?: boolean
|
||||||
offset: number
|
offset: number
|
||||||
updateObject: (object: Character | Weapon | Summon, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExtraSummons = (props: Props) => {
|
const ExtraSummons = (props: Props) => {
|
||||||
const numSummons: number = 2
|
const numSummons: number = 2
|
||||||
|
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="ExtraSummons">
|
<div id="ExtraSummons">
|
||||||
<span>{t('summons.subaura')}</span>
|
<span>{t("summons.subaura")}</span>
|
||||||
<ul id="grid_summons">
|
<ul id="grid_summons">
|
||||||
{
|
{Array.from(Array(numSummons)).map((x, i) => {
|
||||||
Array.from(Array(numSummons)).map((x, i) => {
|
return (
|
||||||
return (
|
<li key={`grid_unit_${i}`}>
|
||||||
<li key={`grid_unit_${i}`} >
|
<SummonUnit
|
||||||
<SummonUnit
|
editable={props.editable}
|
||||||
editable={props.editable}
|
position={props.offset + i}
|
||||||
position={props.offset + i}
|
unitType={1}
|
||||||
unitType={1}
|
gridSummon={props.grid[props.offset + i]}
|
||||||
gridSummon={props.grid[props.offset + i]}
|
updateObject={props.updateObject}
|
||||||
updateObject={props.updateObject}
|
updateUncap={props.updateUncap}
|
||||||
updateUncap={props.updateUncap}
|
/>
|
||||||
/>
|
</li>
|
||||||
</li>
|
)
|
||||||
)
|
})}
|
||||||
})
|
</ul>
|
||||||
}
|
</div>
|
||||||
</ul>
|
)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExtraSummons
|
export default ExtraSummons
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,113 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useEffect, useState } from "react"
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from "next/router"
|
||||||
|
import { useSnapshot } from "valtio"
|
||||||
|
|
||||||
import api from '~utils/api'
|
import { appState } from "~utils/appState"
|
||||||
import { appState } from '~utils/appState'
|
import { jobGroups } from "~utils/jobGroups"
|
||||||
import { jobGroups } from '~utils/jobGroups'
|
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
currentJob?: string
|
currentJob?: string
|
||||||
onChange?: (job?: Job) => void
|
onChange?: (job?: Job) => void
|
||||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupedJob = { [key: string]: Job[] }
|
type GroupedJob = { [key: string]: Job[] }
|
||||||
|
|
||||||
const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
|
function useFieldSet(props, ref) {
|
||||||
// Set up router for locale
|
// Set up router for locale
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || 'en'
|
const locale = router.locale || "en"
|
||||||
|
|
||||||
|
// Create snapshot of app state
|
||||||
|
const { party } = useSnapshot(appState)
|
||||||
|
|
||||||
// Set up local states for storing jobs
|
// Set up local states for storing jobs
|
||||||
const [currentJob, setCurrentJob] = useState<Job>()
|
const [currentJob, setCurrentJob] = useState<Job>()
|
||||||
const [jobs, setJobs] = useState<Job[]>()
|
const [jobs, setJobs] = useState<Job[]>()
|
||||||
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
|
const [sortedJobs, setSortedJobs] = useState<GroupedJob>()
|
||||||
|
|
||||||
// Organize jobs into groups on mount
|
// Set current job from state on mount
|
||||||
const organizeJobs = useCallback((jobs: Job[]) => {
|
useEffect(() => {
|
||||||
const jobGroups = jobs.map(job => job.row).filter((value, index, self) => self.indexOf(value) === index)
|
setCurrentJob(party.job)
|
||||||
let groupedJobs: GroupedJob = {}
|
|
||||||
|
|
||||||
jobGroups.forEach(group => {
|
|
||||||
groupedJobs[group] = jobs.filter(job => job.row === group)
|
|
||||||
})
|
|
||||||
|
|
||||||
setJobs(jobs)
|
|
||||||
setSortedJobs(groupedJobs)
|
|
||||||
appState.jobs = jobs
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Fetch all jobs on mount
|
// Organize jobs into groups on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.endpoints.jobs.getAll()
|
const jobGroups = appState.jobs
|
||||||
.then(response => organizeJobs(response.data))
|
.map((job) => job.row)
|
||||||
}, [organizeJobs])
|
.filter((value, index, self) => self.indexOf(value) === index)
|
||||||
|
let groupedJobs: GroupedJob = {}
|
||||||
|
|
||||||
|
jobGroups.forEach((group) => {
|
||||||
|
groupedJobs[group] = appState.jobs.filter((job) => job.row === group)
|
||||||
|
})
|
||||||
|
|
||||||
|
setJobs(appState.jobs)
|
||||||
|
setSortedJobs(groupedJobs)
|
||||||
|
}, [appState])
|
||||||
|
|
||||||
// Set current job on mount
|
// Set current job on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (jobs && props.currentJob) {
|
if (jobs && props.currentJob) {
|
||||||
const job = jobs.find(job => job.id === props.currentJob)
|
const job = appState.jobs.find((job) => job.id === props.currentJob)
|
||||||
setCurrentJob(job)
|
setCurrentJob(job)
|
||||||
}
|
}
|
||||||
}, [jobs, props.currentJob])
|
}, [appState, props.currentJob])
|
||||||
|
|
||||||
// Enable changing select value
|
// Enable changing select value
|
||||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
if (jobs) {
|
if (jobs) {
|
||||||
const job = jobs.find(job => job.id === event.target.value)
|
const job = jobs.find((job) => job.id === event.target.value)
|
||||||
if (props.onChange) props.onChange(job)
|
if (props.onChange) props.onChange(job)
|
||||||
setCurrentJob(job)
|
setCurrentJob(job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render JSX for each job option, sorted into optgroups
|
// Render JSX for each job option, sorted into optgroups
|
||||||
function renderJobGroup(group: string) {
|
function renderJobGroup(group: string) {
|
||||||
const options = sortedJobs && sortedJobs[group].length > 0 &&
|
const options =
|
||||||
sortedJobs[group].sort((a, b) => a.order - b.order).map((item, i) => {
|
sortedJobs &&
|
||||||
return (
|
sortedJobs[group].length > 0 &&
|
||||||
<option key={i} value={item.id}>{item.name[locale]}</option>
|
sortedJobs[group]
|
||||||
)
|
.sort((a, b) => a.order - b.order)
|
||||||
})
|
.map((item, i) => {
|
||||||
|
return (
|
||||||
|
<option key={i} value={item.id}>
|
||||||
|
{item.name[locale]}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const groupName = jobGroups.find(g => g.slug === group)?.name[locale]
|
const groupName = jobGroups.find((g) => g.slug === group)?.name[locale]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<optgroup key={group} label={groupName}>
|
<optgroup key={group} label={groupName}>
|
||||||
{options}
|
{options}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
key={currentJob?.id}
|
key={currentJob ? currentJob.id : -1}
|
||||||
value={currentJob?.id}
|
value={currentJob ? currentJob.id : -1}
|
||||||
onBlur={props.onBlur}
|
onBlur={props.onBlur}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ref={ref}>
|
ref={ref}
|
||||||
<option key="no-job" value={-1}>No class</option>
|
>
|
||||||
{ (sortedJobs) ? Object.keys(sortedJobs).map(x => renderJobGroup(x)) : '' }
|
<option key="no-job" value={-1}>
|
||||||
</select>
|
No class
|
||||||
|
</option>
|
||||||
|
{sortedJobs
|
||||||
|
? Object.keys(sortedJobs).map((x) => renderJobGroup(x))
|
||||||
|
: ""}
|
||||||
|
</select>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default JobDropdown
|
export default JobDropdown
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,73 @@
|
||||||
#Job {
|
#Job {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: $unit * 3;
|
||||||
|
|
||||||
|
select {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.JobDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: $unit * 3;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $medium;
|
||||||
|
padding: $unit 0 $unit * 2;
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
flex-grow: 1;
|
flex-grow: 0;
|
||||||
width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.JobImage {
|
.JobSkills {
|
||||||
$height: 249px;
|
flex-grow: 2;
|
||||||
$width: 447px;
|
|
||||||
|
|
||||||
background: url('/images/background_a.jpg');
|
|
||||||
background-size: 500px 281px;
|
|
||||||
border-radius: $unit;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
|
||||||
display: block;
|
|
||||||
flex-grow: 2;
|
|
||||||
height: $height;
|
|
||||||
margin-right: $unit * 3;
|
|
||||||
max-height: $height;
|
|
||||||
max-width: $width;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
width: $width;
|
|
||||||
transition: box-shadow 0.15s ease-in-out;
|
|
||||||
|
|
||||||
img {
|
|
||||||
position: relative;
|
|
||||||
top: $unit * -4;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Overlay {
|
|
||||||
background: rgba(255, 255, 255, 0.12);
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.JobImage {
|
||||||
|
$height: 249px;
|
||||||
|
$width: 447px;
|
||||||
|
|
||||||
|
background: url("/images/background_a.jpg");
|
||||||
|
background-size: 500px 281px;
|
||||||
|
border-radius: $unit;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
display: block;
|
||||||
|
flex-grow: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: $height;
|
||||||
|
margin-right: $unit * 3;
|
||||||
|
max-height: $height;
|
||||||
|
max-width: $width;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: $width;
|
||||||
|
transition: box-shadow 0.15s ease-in-out;
|
||||||
|
|
||||||
|
img {
|
||||||
|
-webkit-filter: drop-shadow(4px 4px 8px rgba(0, 0, 0, 0.48));
|
||||||
|
filter: drop-shadow(4px 4px 8px rgba(0, 0, 0, 0.48));
|
||||||
|
position: relative;
|
||||||
|
top: $unit * -4;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Overlay {
|
||||||
|
background: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.JobSkills {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,165 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { ForwardedRef, useEffect, useState } from "react"
|
||||||
import { useSnapshot } from 'valtio'
|
import { useRouter } from "next/router"
|
||||||
|
import { useSnapshot } from "valtio"
|
||||||
|
import { useTranslation } from "next-i18next"
|
||||||
|
|
||||||
import JobDropdown from '~components/JobDropdown'
|
import JobDropdown from "~components/JobDropdown"
|
||||||
|
import JobSkillItem from "~components/JobSkillItem"
|
||||||
|
import SearchModal from "~components/SearchModal"
|
||||||
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
import './index.scss'
|
import type { JobSkillObject, SearchableObject } from "~types"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {}
|
interface Props {
|
||||||
|
job?: Job
|
||||||
const JobSection = (props: Props) => {
|
jobSkills: JobSkillObject
|
||||||
const [job, setJob] = useState<Job>()
|
editable: boolean
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
saveJob: (job: Job) => void
|
||||||
|
saveSkill: (skill: JobSkill, position: number) => void
|
||||||
const { party } = useSnapshot(appState)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Set current job based on ID
|
|
||||||
setJob(party.job)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
generateImageUrl()
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (job) appState.party.job = job
|
|
||||||
}, [job])
|
|
||||||
|
|
||||||
function receiveJob(job?: Job) {
|
|
||||||
setJob(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateImageUrl() {
|
|
||||||
let imgSrc = ""
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
const slug = job?.name.en.replaceAll(' ', '-').toLowerCase()
|
|
||||||
const gender = (party.user && party.user.gender == 1) ? 'b' : 'a'
|
|
||||||
|
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
|
|
||||||
}
|
|
||||||
|
|
||||||
setImageUrl(imgSrc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render: JSX components
|
|
||||||
return (
|
|
||||||
<section id="Job">
|
|
||||||
<div className="JobImage">
|
|
||||||
<img src={imageUrl} />
|
|
||||||
<div className="Overlay" />
|
|
||||||
</div>
|
|
||||||
<JobDropdown
|
|
||||||
currentJob={ (party.job) ? party.job.id : undefined}
|
|
||||||
onChange={receiveJob}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JobSection
|
const JobSection = (props: Props) => {
|
||||||
|
const { party } = useSnapshot(appState)
|
||||||
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||||
|
|
||||||
|
const [job, setJob] = useState<Job>()
|
||||||
|
const [imageUrl, setImageUrl] = useState("")
|
||||||
|
const [numSkills, setNumSkills] = useState(4)
|
||||||
|
const [skills, setSkills] = useState<{ [key: number]: JobSkill | undefined }>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const selectRef = React.createRef<HTMLSelectElement>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Set current job based on ID
|
||||||
|
if (props.job) {
|
||||||
|
setJob(props.job)
|
||||||
|
setSkills({
|
||||||
|
0: props.jobSkills[0],
|
||||||
|
1: props.jobSkills[1],
|
||||||
|
2: props.jobSkills[2],
|
||||||
|
3: props.jobSkills[3],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selectRef.current) selectRef.current.value = props.job.id
|
||||||
|
}
|
||||||
|
}, [props])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateImageUrl()
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (job) {
|
||||||
|
if ((party.job && job.id != party.job.id) || !party.job)
|
||||||
|
appState.party.job = job
|
||||||
|
if (job.row === "1") setNumSkills(3)
|
||||||
|
else setNumSkills(4)
|
||||||
|
}
|
||||||
|
}, [job])
|
||||||
|
|
||||||
|
function receiveJob(job?: Job) {
|
||||||
|
if (job) {
|
||||||
|
setJob(job)
|
||||||
|
props.saveJob(job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateImageUrl() {
|
||||||
|
let imgSrc = ""
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
const slug = job?.name.en.replaceAll(" ", "-").toLowerCase()
|
||||||
|
const gender = party.user && party.user.gender == 1 ? "b" : "a"
|
||||||
|
|
||||||
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/jobs/${slug}_${gender}.png`
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageUrl(imgSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canEditSkill = (skill?: JobSkill) => {
|
||||||
|
if (job && skill) {
|
||||||
|
if (skill.job.id === job.id && skill.main && !skill.sub) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.editable
|
||||||
|
}
|
||||||
|
|
||||||
|
const skillItem = (index: number, editable: boolean) => {
|
||||||
|
return (
|
||||||
|
<JobSkillItem
|
||||||
|
skill={skills[index]}
|
||||||
|
editable={canEditSkill(skills[index])}
|
||||||
|
key={`skill-${index}`}
|
||||||
|
hasJob={job != undefined && job.id != "-1"}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editableSkillItem = (index: number) => {
|
||||||
|
return (
|
||||||
|
<SearchModal
|
||||||
|
placeholderText={t("search.placeholders.job_skill")}
|
||||||
|
fromPosition={index}
|
||||||
|
object="job_skills"
|
||||||
|
job={job}
|
||||||
|
send={saveJobSkill}
|
||||||
|
>
|
||||||
|
{skillItem(index, true)}
|
||||||
|
</SearchModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveJobSkill(object: SearchableObject, position: number) {
|
||||||
|
const skill = object as JobSkill
|
||||||
|
|
||||||
|
const newSkills = skills
|
||||||
|
newSkills[position] = skill
|
||||||
|
setSkills(newSkills)
|
||||||
|
|
||||||
|
props.saveSkill(skill, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render: JSX components
|
||||||
|
return (
|
||||||
|
<section id="Job">
|
||||||
|
<div className="JobImage">
|
||||||
|
<img src={imageUrl} />
|
||||||
|
<div className="Overlay" />
|
||||||
|
</div>
|
||||||
|
<div className="JobDetails">
|
||||||
|
{props.editable ? (
|
||||||
|
<JobDropdown
|
||||||
|
currentJob={party.job?.id}
|
||||||
|
onChange={receiveJob}
|
||||||
|
ref={selectRef}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<h3>{party.job?.name[locale]}</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ul className="JobSkills">
|
||||||
|
{[...Array(numSkills)].map((e, i) => (
|
||||||
|
<li key={`job-${i}`}>
|
||||||
|
{canEditSkill(skills[i])
|
||||||
|
? editableSkillItem(i)
|
||||||
|
: skillItem(i, false)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobSection
|
||||||
|
|
|
||||||
46
components/JobSkillItem/index.scss
Normal file
46
components/JobSkillItem/index.scss
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
.JobSkill {
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.editable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& > img.editable,
|
||||||
|
& > div.placeholder.editable {
|
||||||
|
border: $hover-stroke;
|
||||||
|
box-shadow: $hover-shadow;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: $scale-tall;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p.placeholder {
|
||||||
|
color: $grey-20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > img,
|
||||||
|
& > div.placeholder {
|
||||||
|
background: white;
|
||||||
|
border-radius: calc($unit / 2);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
|
width: $unit * 5;
|
||||||
|
height: $unit * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div.placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
fill: $grey-60;
|
||||||
|
width: $unit * 2;
|
||||||
|
height: $unit * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.placeholder {
|
||||||
|
color: $grey-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
components/JobSkillItem/index.tsx
Normal file
81
components/JobSkillItem/index.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import React from "react"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { useTranslation } from "next-i18next"
|
||||||
|
|
||||||
|
import classNames from "classnames"
|
||||||
|
import PlusIcon from "~public/icons/Add.svg"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props extends React.ComponentPropsWithoutRef<"div"> {
|
||||||
|
skill?: JobSkill
|
||||||
|
editable: boolean
|
||||||
|
hasJob: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
|
||||||
|
({ ...props }, forwardedRef) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { t } = useTranslation("common")
|
||||||
|
const locale =
|
||||||
|
router.locale && ["en", "ja"].includes(router.locale)
|
||||||
|
? router.locale
|
||||||
|
: "en"
|
||||||
|
|
||||||
|
const classes = classNames({
|
||||||
|
JobSkill: true,
|
||||||
|
editable: props.editable,
|
||||||
|
})
|
||||||
|
|
||||||
|
const imageClasses = classNames({
|
||||||
|
placeholder: !props.skill,
|
||||||
|
editable: props.editable && props.hasJob,
|
||||||
|
})
|
||||||
|
|
||||||
|
const skillImage = () => {
|
||||||
|
let jsx: React.ReactNode
|
||||||
|
|
||||||
|
if (props.skill) {
|
||||||
|
jsx = (
|
||||||
|
<img
|
||||||
|
alt={props.skill.name[locale]}
|
||||||
|
className={imageClasses}
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}job-skills/${props.skill.slug}.png`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
jsx = (
|
||||||
|
<div className={imageClasses}>
|
||||||
|
{props.editable && props.hasJob ? <PlusIcon /> : ""}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = () => {
|
||||||
|
let jsx: React.ReactNode
|
||||||
|
|
||||||
|
if (props.skill) {
|
||||||
|
jsx = <p>{props.skill.name[locale]}</p>
|
||||||
|
} else if (props.editable && props.hasJob) {
|
||||||
|
jsx = <p className="placeholder">{t("job_skills.state.selectable")}</p>
|
||||||
|
} else {
|
||||||
|
jsx = <p className="placeholder">{t("job_skills.state.no_skill")}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes} onClick={props.onClick} ref={forwardedRef}>
|
||||||
|
{skillImage()}
|
||||||
|
{label()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default JobSkillItem
|
||||||
71
components/JobSkillResult/index.scss
Normal file
71
components/JobSkillResult/index.scss
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
.JobSkillResult {
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit * 1.5;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $grey-90;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.Info .skill.pill {
|
||||||
|
background: $grey-80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: calc($unit / 2);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.skill.pill {
|
||||||
|
background: $grey-90;
|
||||||
|
border-radius: $unit * 2;
|
||||||
|
color: $grey-00;
|
||||||
|
display: inline;
|
||||||
|
font-size: $font-tiny;
|
||||||
|
font-weight: $medium;
|
||||||
|
padding: calc($unit / 2) $unit;
|
||||||
|
|
||||||
|
&.buffing {
|
||||||
|
background-color: $light-bg-dark;
|
||||||
|
color: $light-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.debuffing {
|
||||||
|
background-color: $water-bg-dark;
|
||||||
|
color: $water-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.healing {
|
||||||
|
background-color: $wind-bg-dark;
|
||||||
|
color: $wind-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.damaging {
|
||||||
|
background-color: $fire-bg-dark;
|
||||||
|
color: $fire-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.field {
|
||||||
|
background-color: $dark-bg-dark;
|
||||||
|
color: $dark-text-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
color: #555;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $medium;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: $unit * 6;
|
||||||
|
height: $unit * 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
components/JobSkillResult/index.tsx
Normal file
41
components/JobSkillResult/index.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { SkillGroup, skillClassification } from "~utils/skillGroups"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: JobSkill
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobSkillResult = (props: Props) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||||
|
|
||||||
|
const skill = props.data
|
||||||
|
|
||||||
|
const [group, setGroup] = useState<SkillGroup | undefined>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setGroup(skillClassification.find((group) => group.id === skill.color))
|
||||||
|
}, [skill, setGroup, skillClassification])
|
||||||
|
|
||||||
|
const jobSkillUrl = () =>
|
||||||
|
`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-skills/${skill.slug}.png`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="JobSkillResult" onClick={props.onClick}>
|
||||||
|
<img alt={skill.name[locale]} src={jobSkillUrl()} />
|
||||||
|
<div className="Info">
|
||||||
|
<h5>{skill.name[locale]}</h5>
|
||||||
|
<div className={`skill pill ${group?.name["en"].toLowerCase()}`}>
|
||||||
|
{group?.name[locale]}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobSkillResult
|
||||||
3
components/JobSkillSearchFilterBar/index.scss
Normal file
3
components/JobSkillSearchFilterBar/index.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.SearchFilterBar select {
|
||||||
|
background-color: $grey-90;
|
||||||
|
}
|
||||||
71
components/JobSkillSearchFilterBar/index.tsx
Normal file
71
components/JobSkillSearchFilterBar/index.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { skillGroups } from "~utils/skillGroups"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sendFilters: (filters: { [key: string]: number }) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobSkillSearchFilterBar = (props: Props) => {
|
||||||
|
// Set up translation
|
||||||
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
|
const [currentGroup, setCurrentGroup] = useState(-1)
|
||||||
|
|
||||||
|
function onChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
|
setCurrentGroup(parseInt(event.target.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBlur(event: React.ChangeEvent<HTMLSelectElement>) {}
|
||||||
|
|
||||||
|
function sendFilters() {
|
||||||
|
const filters = {
|
||||||
|
group: currentGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
props.sendFilters(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sendFilters()
|
||||||
|
}, [currentGroup])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SearchFilterBar">
|
||||||
|
<select
|
||||||
|
key="job-skill-groups"
|
||||||
|
value={currentGroup}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChange={onChange}
|
||||||
|
>
|
||||||
|
<option key="all" value={-1}>
|
||||||
|
{t(`job_skills.all`)}
|
||||||
|
</option>
|
||||||
|
<option key="damaging" value={2}>
|
||||||
|
{t(`job_skills.damaging`)}
|
||||||
|
</option>
|
||||||
|
<option key="buffing" value={0}>
|
||||||
|
{t(`job_skills.buffing`)}
|
||||||
|
</option>
|
||||||
|
<option key="debuffing" value={1}>
|
||||||
|
{t(`job_skills.debuffing`)}
|
||||||
|
</option>
|
||||||
|
<option key="healing" value={3}>
|
||||||
|
{t(`job_skills.healing`)}
|
||||||
|
</option>
|
||||||
|
<option key="emp" value={4}>
|
||||||
|
{t(`job_skills.emp`)}
|
||||||
|
</option>
|
||||||
|
<option key="base" value={5}>
|
||||||
|
{t(`job_skills.base`)}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobSkillSearchFilterBar
|
||||||
|
|
@ -42,9 +42,6 @@ const Party = (props: Props) => {
|
||||||
|
|
||||||
// Set up states
|
// Set up states
|
||||||
const { party } = useSnapshot(appState)
|
const { party } = useSnapshot(appState)
|
||||||
const jobState = party.job
|
|
||||||
|
|
||||||
const [job, setJob] = useState<Job>()
|
|
||||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
||||||
|
|
||||||
// Reset state on first load
|
// Reset state on first load
|
||||||
|
|
@ -54,14 +51,6 @@ const Party = (props: Props) => {
|
||||||
if (props.team) storeParty(props.team)
|
if (props.team) storeParty(props.team)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setJob(jobState)
|
|
||||||
}, [jobState])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
jobChanged()
|
|
||||||
}, [job])
|
|
||||||
|
|
||||||
// Methods: Creating a new party
|
// Methods: Creating a new party
|
||||||
async function createParty(extra: boolean = false) {
|
async function createParty(extra: boolean = false) {
|
||||||
let body = {
|
let body = {
|
||||||
|
|
@ -89,18 +78,6 @@ const Party = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function jobChanged() {
|
|
||||||
if (party.id && appState.party.editable) {
|
|
||||||
api.endpoints.parties.update(
|
|
||||||
party.id,
|
|
||||||
{
|
|
||||||
party: { job_id: job ? job.id : "" },
|
|
||||||
},
|
|
||||||
headers
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDetails(name?: string, description?: string, raid?: Raid) {
|
function updateDetails(name?: string, description?: string, raid?: Raid) {
|
||||||
if (
|
if (
|
||||||
appState.party.name !== name ||
|
appState.party.name !== name ||
|
||||||
|
|
@ -160,6 +137,8 @@ const Party = (props: Props) => {
|
||||||
appState.party.description = party.description
|
appState.party.description = party.description
|
||||||
appState.party.raid = party.raid
|
appState.party.raid = party.raid
|
||||||
appState.party.updated_at = party.updated_at
|
appState.party.updated_at = party.updated_at
|
||||||
|
appState.party.job = party.job
|
||||||
|
appState.party.jobSkills = party.job_skills
|
||||||
|
|
||||||
appState.party.id = party.id
|
appState.party.id = party.id
|
||||||
appState.party.extra = party.extra
|
appState.party.extra = party.extra
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import React, { useEffect, useRef, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { getCookie, setCookie } from "cookies-next"
|
import { getCookie, setCookie } from "cookies-next"
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import { useSnapshot } from "valtio"
|
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import InfiniteScroll from "react-infinite-scroll-component"
|
import InfiniteScroll from "react-infinite-scroll-component"
|
||||||
|
|
||||||
import { appState } from "~utils/appState"
|
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
|
|
||||||
import * as Dialog from "@radix-ui/react-dialog"
|
import * as Dialog from "@radix-ui/react-dialog"
|
||||||
|
|
@ -13,27 +11,29 @@ import * as Dialog from "@radix-ui/react-dialog"
|
||||||
import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar"
|
import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar"
|
||||||
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"
|
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar"
|
||||||
import SummonSearchFilterBar from "~components/SummonSearchFilterBar"
|
import SummonSearchFilterBar from "~components/SummonSearchFilterBar"
|
||||||
|
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar"
|
||||||
|
|
||||||
import CharacterResult from "~components/CharacterResult"
|
import CharacterResult from "~components/CharacterResult"
|
||||||
import WeaponResult from "~components/WeaponResult"
|
import WeaponResult from "~components/WeaponResult"
|
||||||
import SummonResult from "~components/SummonResult"
|
import SummonResult from "~components/SummonResult"
|
||||||
|
import JobSkillResult from "~components/JobSkillResult"
|
||||||
|
|
||||||
|
import type { SearchableObject, SearchableObjectArray } from "~types"
|
||||||
|
|
||||||
import "./index.scss"
|
import "./index.scss"
|
||||||
import CrossIcon from "~public/icons/Cross.svg"
|
import CrossIcon from "~public/icons/Cross.svg"
|
||||||
import cloneDeep from "lodash.clonedeep"
|
import cloneDeep from "lodash.clonedeep"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
send: (object: Character | Weapon | Summon, position: number) => any
|
send: (object: SearchableObject, position: number) => any
|
||||||
placeholderText: string
|
placeholderText: string
|
||||||
fromPosition: number
|
fromPosition: number
|
||||||
object: "weapons" | "characters" | "summons"
|
job?: Job
|
||||||
|
object: "weapons" | "characters" | "summons" | "job_skills"
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchModal = (props: Props) => {
|
const SearchModal = (props: Props) => {
|
||||||
// Set up snapshot of app state
|
|
||||||
let { grid, search } = useSnapshot(appState)
|
|
||||||
|
|
||||||
// Set up router
|
// Set up router
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale
|
const locale = router.locale
|
||||||
|
|
@ -45,23 +45,16 @@ const SearchModal = (props: Props) => {
|
||||||
let scrollContainer = React.createRef<HTMLDivElement>()
|
let scrollContainer = React.createRef<HTMLDivElement>()
|
||||||
|
|
||||||
const [firstLoad, setFirstLoad] = useState(true)
|
const [firstLoad, setFirstLoad] = useState(true)
|
||||||
const [objects, setObjects] = useState<{
|
const [filters, setFilters] = useState<{ [key: string]: any }>()
|
||||||
[id: number]: GridCharacter | GridWeapon | GridSummon | undefined
|
|
||||||
}>()
|
|
||||||
const [filters, setFilters] = useState<{ [key: string]: number[] }>()
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [query, setQuery] = useState("")
|
const [query, setQuery] = useState("")
|
||||||
const [results, setResults] = useState<(Weapon | Summon | Character)[]>([])
|
const [results, setResults] = useState<SearchableObjectArray>([])
|
||||||
|
|
||||||
// Pagination states
|
// Pagination states
|
||||||
const [recordCount, setRecordCount] = useState(0)
|
const [recordCount, setRecordCount] = useState(0)
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [totalPages, setTotalPages] = useState(1)
|
const [totalPages, setTotalPages] = useState(1)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setObjects(grid[props.object])
|
|
||||||
}, [grid, props.object])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchInput.current) searchInput.current.focus()
|
if (searchInput.current) searchInput.current.focus()
|
||||||
}, [searchInput])
|
}, [searchInput])
|
||||||
|
|
@ -80,6 +73,7 @@ const SearchModal = (props: Props) => {
|
||||||
.search({
|
.search({
|
||||||
object: props.object,
|
object: props.object,
|
||||||
query: query,
|
query: query,
|
||||||
|
job: props.job?.id,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
|
|
@ -99,10 +93,7 @@ const SearchModal = (props: Props) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceResults(
|
function replaceResults(count: number, list: SearchableObjectArray) {
|
||||||
count: number,
|
|
||||||
list: Weapon[] | Summon[] | Character[]
|
|
||||||
) {
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
setResults(list)
|
setResults(list)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -110,26 +101,36 @@ const SearchModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendResults(list: Weapon[] | Summon[] | Character[]) {
|
function appendResults(list: SearchableObjectArray) {
|
||||||
setResults([...results, ...list])
|
setResults([...results, ...list])
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeRecentResult(result: Character | Weapon | Summon) {
|
function storeRecentResult(result: SearchableObject) {
|
||||||
const key = `recent_${props.object}`
|
const key = `recent_${props.object}`
|
||||||
const cookie = getCookie(key)
|
const cookie = getCookie(key)
|
||||||
const cookieObj: Character[] | Weapon[] | Summon[] = cookie
|
const cookieObj: SearchableObjectArray = cookie
|
||||||
? JSON.parse(cookie as string)
|
? JSON.parse(cookie as string)
|
||||||
: []
|
: []
|
||||||
let recents: Character[] | Weapon[] | Summon[] = []
|
let recents: SearchableObjectArray = []
|
||||||
|
|
||||||
if (props.object === "weapons") {
|
if (props.object === "weapons") {
|
||||||
recents = cloneDeep(cookieObj as Weapon[]) || []
|
recents = cloneDeep(cookieObj as Weapon[]) || []
|
||||||
if (!recents.find((item) => item.granblue_id === result.granblue_id)) {
|
if (
|
||||||
|
!recents.find(
|
||||||
|
(item) =>
|
||||||
|
(item as Weapon).granblue_id === (result as Weapon).granblue_id
|
||||||
|
)
|
||||||
|
) {
|
||||||
recents.unshift(result as Weapon)
|
recents.unshift(result as Weapon)
|
||||||
}
|
}
|
||||||
} else if (props.object === "summons") {
|
} else if (props.object === "summons") {
|
||||||
recents = cloneDeep(cookieObj as Summon[]) || []
|
recents = cloneDeep(cookieObj as Summon[]) || []
|
||||||
if (!recents.find((item) => item.granblue_id === result.granblue_id)) {
|
if (
|
||||||
|
!recents.find(
|
||||||
|
(item) =>
|
||||||
|
(item as Summon).granblue_id === (result as Summon).granblue_id
|
||||||
|
)
|
||||||
|
) {
|
||||||
recents.unshift(result as Summon)
|
recents.unshift(result as Summon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,12 +140,12 @@ const SearchModal = (props: Props) => {
|
||||||
sendData(result)
|
sendData(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData(result: Character | Weapon | Summon) {
|
function sendData(result: SearchableObject) {
|
||||||
props.send(result, props.fromPosition)
|
props.send(result, props.fromPosition)
|
||||||
openChange()
|
openChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveFilters(filters: { [key: string]: number[] }) {
|
function receiveFilters(filters: { [key: string]: any }) {
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
setResults([])
|
setResults([])
|
||||||
setFilters(filters)
|
setFilters(filters)
|
||||||
|
|
@ -200,6 +201,9 @@ const SearchModal = (props: Props) => {
|
||||||
case "characters":
|
case "characters":
|
||||||
jsx = renderCharacterSearchResults(results)
|
jsx = renderCharacterSearchResults(results)
|
||||||
break
|
break
|
||||||
|
case "job_skills":
|
||||||
|
jsx = renderJobSkillSearchResults(results)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -278,6 +282,27 @@ const SearchModal = (props: Props) => {
|
||||||
return jsx
|
return jsx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderJobSkillSearchResults(results: { [key: string]: any }) {
|
||||||
|
let jsx: React.ReactNode
|
||||||
|
|
||||||
|
const castResults: JobSkill[] = results as JobSkill[]
|
||||||
|
if (castResults && Object.keys(castResults).length > 0) {
|
||||||
|
jsx = castResults.map((result: JobSkill) => {
|
||||||
|
return (
|
||||||
|
<JobSkillResult
|
||||||
|
key={result.id}
|
||||||
|
data={result}
|
||||||
|
onClick={() => {
|
||||||
|
storeRecentResult(result)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx
|
||||||
|
}
|
||||||
|
|
||||||
function openChange() {
|
function openChange() {
|
||||||
if (open) {
|
if (open) {
|
||||||
setQuery("")
|
setQuery("")
|
||||||
|
|
@ -330,6 +355,11 @@ const SearchModal = (props: Props) => {
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
{props.object === "job_skills" ? (
|
||||||
|
<JobSkillSearchFilterBar sendFilters={receiveFilters} />
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="Results" ref={scrollContainer}>
|
<div id="Results" ref={scrollContainer}>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import ExtraSummons from "~components/ExtraSummons"
|
||||||
|
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from "~utils/appState"
|
||||||
|
import type { SearchableObject } from "~types"
|
||||||
|
|
||||||
import "./index.scss"
|
import "./index.scss"
|
||||||
|
|
||||||
|
|
@ -83,10 +84,7 @@ const SummonGrid = (props: Props) => {
|
||||||
])
|
])
|
||||||
|
|
||||||
// Methods: Adding an object from search
|
// Methods: Adding an object from search
|
||||||
function receiveSummonFromSearch(
|
function receiveSummonFromSearch(object: SearchableObject, position: number) {
|
||||||
object: Character | Weapon | Summon,
|
|
||||||
position: number
|
|
||||||
) {
|
|
||||||
const summon = object as Summon
|
const summon = object as Summon
|
||||||
|
|
||||||
if (!party.id) {
|
if (!party.id) {
|
||||||
|
|
|
||||||
|
|
@ -1,120 +1,143 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from "react"
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from "next/router"
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from "next-i18next"
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames"
|
||||||
|
|
||||||
import SearchModal from '~components/SearchModal'
|
import SearchModal from "~components/SearchModal"
|
||||||
import SummonHovercard from '~components/SummonHovercard'
|
import SummonHovercard from "~components/SummonHovercard"
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from "~components/UncapIndicator"
|
||||||
import PlusIcon from '~public/icons/Add.svg'
|
import PlusIcon from "~public/icons/Add.svg"
|
||||||
|
|
||||||
import './index.scss'
|
import type { SearchableObject } from "~types"
|
||||||
|
|
||||||
|
import "./index.scss"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridSummon: GridSummon | undefined
|
gridSummon: GridSummon | undefined
|
||||||
unitType: 0 | 1 | 2
|
unitType: 0 | 1 | 2
|
||||||
position: number
|
position: number
|
||||||
editable: boolean
|
editable: boolean
|
||||||
updateObject: (object: Character | Weapon | Summon, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonUnit = (props: Props) => {
|
const SummonUnit = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
|
||||||
|
|
||||||
const router = useRouter()
|
const [imageUrl, setImageUrl] = useState("")
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
|
||||||
|
|
||||||
const classes = classnames({
|
const router = useRouter()
|
||||||
SummonUnit: true,
|
const locale =
|
||||||
'main': props.unitType == 0,
|
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||||
'grid': props.unitType == 1,
|
|
||||||
'friend': props.unitType == 2,
|
|
||||||
'editable': props.editable,
|
|
||||||
'filled': (props.gridSummon !== undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
const gridSummon = props.gridSummon
|
const classes = classnames({
|
||||||
const summon = gridSummon?.object
|
SummonUnit: true,
|
||||||
|
main: props.unitType == 0,
|
||||||
|
grid: props.unitType == 1,
|
||||||
|
friend: props.unitType == 2,
|
||||||
|
editable: props.editable,
|
||||||
|
filled: props.gridSummon !== undefined,
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
const gridSummon = props.gridSummon
|
||||||
generateImageUrl()
|
const summon = gridSummon?.object
|
||||||
})
|
|
||||||
|
|
||||||
function generateImageUrl() {
|
useEffect(() => {
|
||||||
let imgSrc = ""
|
generateImageUrl()
|
||||||
if (props.gridSummon) {
|
})
|
||||||
const summon = props.gridSummon.object!
|
|
||||||
|
|
||||||
const upgradedSummons = [
|
function generateImageUrl() {
|
||||||
'2040094000', '2040100000', '2040080000', '2040098000',
|
let imgSrc = ""
|
||||||
'2040090000', '2040084000', '2040003000', '2040056000',
|
if (props.gridSummon) {
|
||||||
'2040020000', '2040034000', '2040028000', '2040027000',
|
const summon = props.gridSummon.object!
|
||||||
'2040046000', '2040047000'
|
|
||||||
]
|
const upgradedSummons = [
|
||||||
|
"2040094000",
|
||||||
let suffix = ''
|
"2040100000",
|
||||||
if (upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && props.gridSummon.uncap_level == 5)
|
"2040080000",
|
||||||
suffix = '_02'
|
"2040098000",
|
||||||
|
"2040090000",
|
||||||
// Generate the correct source for the summon
|
"2040084000",
|
||||||
if (props.unitType == 0 || props.unitType == 2)
|
"2040003000",
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblue_id}${suffix}.jpg`
|
"2040056000",
|
||||||
else
|
"2040020000",
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
|
"2040034000",
|
||||||
}
|
"2040028000",
|
||||||
|
"2040027000",
|
||||||
setImageUrl(imgSrc)
|
"2040046000",
|
||||||
|
"2040047000",
|
||||||
|
]
|
||||||
|
|
||||||
|
let suffix = ""
|
||||||
|
if (
|
||||||
|
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||||
|
props.gridSummon.uncap_level == 5
|
||||||
|
)
|
||||||
|
suffix = "_02"
|
||||||
|
|
||||||
|
// Generate the correct source for the summon
|
||||||
|
if (props.unitType == 0 || props.unitType == 2)
|
||||||
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblue_id}${suffix}.jpg`
|
||||||
|
else
|
||||||
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
|
||||||
}
|
}
|
||||||
|
|
||||||
function passUncapData(uncap: number) {
|
setImageUrl(imgSrc)
|
||||||
if (props.gridSummon)
|
}
|
||||||
props.updateUncap(props.gridSummon.id, props.position, uncap)
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = (
|
function passUncapData(uncap: number) {
|
||||||
<div className="SummonImage">
|
if (props.gridSummon)
|
||||||
<img alt={summon?.name.en} className="grid_image" src={imageUrl} />
|
props.updateUncap(props.gridSummon.id, props.position, uncap)
|
||||||
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' }
|
}
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const editableImage = (
|
const image = (
|
||||||
<SearchModal
|
<div className="SummonImage">
|
||||||
placeholderText={t('search.placeholders.summon')}
|
<img alt={summon?.name.en} className="grid_image" src={imageUrl} />
|
||||||
fromPosition={props.position}
|
{props.editable ? (
|
||||||
object="summons"
|
<span className="icon">
|
||||||
send={props.updateObject}>
|
<PlusIcon />
|
||||||
{image}
|
</span>
|
||||||
</SearchModal>
|
) : (
|
||||||
)
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const unitContent = (
|
const editableImage = (
|
||||||
<div className={classes}>
|
<SearchModal
|
||||||
{ (props.editable) ? editableImage : image }
|
placeholderText={t("search.placeholders.summon")}
|
||||||
{ (gridSummon) ?
|
fromPosition={props.position}
|
||||||
<UncapIndicator
|
object="summons"
|
||||||
type="summon"
|
send={props.updateObject}
|
||||||
ulb={gridSummon.object.uncap.ulb || false}
|
>
|
||||||
flb={gridSummon.object.uncap.flb || false}
|
{image}
|
||||||
uncapLevel={gridSummon.uncap_level}
|
</SearchModal>
|
||||||
updateUncap={passUncapData}
|
)
|
||||||
special={false}
|
|
||||||
/> : ''
|
|
||||||
}
|
|
||||||
<h3 className="SummonName">{summon?.name[locale]}</h3>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const withHovercard = (
|
const unitContent = (
|
||||||
<SummonHovercard gridSummon={gridSummon!}>
|
<div className={classes}>
|
||||||
{unitContent}
|
{props.editable ? editableImage : image}
|
||||||
</SummonHovercard>
|
{gridSummon ? (
|
||||||
)
|
<UncapIndicator
|
||||||
|
type="summon"
|
||||||
|
ulb={gridSummon.object.uncap.ulb || false}
|
||||||
|
flb={gridSummon.object.uncap.flb || false}
|
||||||
|
uncapLevel={gridSummon.uncap_level}
|
||||||
|
updateUncap={passUncapData}
|
||||||
|
special={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<h3 className="SummonName">{summon?.name[locale]}</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (gridSummon && !props.editable) ? withHovercard : unitContent
|
const withHovercard = (
|
||||||
|
<SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridSummon && !props.editable ? withHovercard : unitContent
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonUnit
|
export default SummonUnit
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,129 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from "react"
|
||||||
import UncapStar from '~components/UncapStar'
|
import UncapStar from "~components/UncapStar"
|
||||||
|
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'character' | 'weapon' | 'summon'
|
type: "character" | "weapon" | "summon"
|
||||||
rarity?: number
|
rarity?: number
|
||||||
uncapLevel?: number
|
uncapLevel?: number
|
||||||
flb: boolean
|
flb: boolean
|
||||||
ulb: boolean
|
ulb: boolean
|
||||||
special: boolean
|
special: boolean
|
||||||
updateUncap?: (uncap: number) => void
|
updateUncap?: (uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const UncapIndicator = (props: Props) => {
|
const UncapIndicator = (props: Props) => {
|
||||||
const [uncap, setUncap] = useState(props.uncapLevel)
|
const [uncap, setUncap] = useState(props.uncapLevel)
|
||||||
|
|
||||||
const numStars = setNumStars()
|
const numStars = setNumStars()
|
||||||
function setNumStars() {
|
function setNumStars() {
|
||||||
let numStars
|
let numStars
|
||||||
|
|
||||||
if (props.type === 'character') {
|
if (props.type === "character") {
|
||||||
if (props.special) {
|
if (props.special) {
|
||||||
if (props.ulb) {
|
if (props.ulb) {
|
||||||
numStars = 5
|
numStars = 5
|
||||||
} else if (props.flb) {
|
} else if (props.flb) {
|
||||||
numStars = 4
|
numStars = 4
|
||||||
} else {
|
|
||||||
numStars = 3
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (props.ulb) {
|
|
||||||
numStars = 6
|
|
||||||
} else if (props.flb) {
|
|
||||||
numStars = 5
|
|
||||||
} else {
|
|
||||||
numStars = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (props.ulb) {
|
numStars = 3
|
||||||
numStars = 5
|
|
||||||
} else if (props.flb) {
|
|
||||||
numStars = 4
|
|
||||||
} else {
|
|
||||||
numStars = 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return numStars
|
if (props.ulb) {
|
||||||
}
|
numStars = 6
|
||||||
|
} else if (props.flb) {
|
||||||
function toggleStar(index: number, empty: boolean) {
|
numStars = 5
|
||||||
if (props.updateUncap) {
|
} else {
|
||||||
if (empty) props.updateUncap(index + 1)
|
numStars = 4
|
||||||
else props.updateUncap(index)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.ulb) {
|
||||||
|
numStars = 5
|
||||||
|
} else if (props.flb) {
|
||||||
|
numStars = 4
|
||||||
|
} else {
|
||||||
|
numStars = 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const transcendence = (i: number) => {
|
return numStars
|
||||||
return <UncapStar ulb={true} empty={ (props.uncapLevel) ? i >= props.uncapLevel : false } key={`star_${i}`} index={i} onClick={toggleStar} />
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const ulb = (i: number) => {
|
function toggleStar(index: number, empty: boolean) {
|
||||||
return <UncapStar ulb={true} empty={ (props.uncapLevel) ? i >= props.uncapLevel : false } key={`star_${i}`} index={i} onClick={toggleStar} />
|
if (props.updateUncap) {
|
||||||
}
|
if (empty) props.updateUncap(index + 1)
|
||||||
|
else props.updateUncap(index)
|
||||||
const flb = (i: number) => {
|
|
||||||
return <UncapStar flb={true} empty={ (props.uncapLevel) ? i >= props.uncapLevel : false } key={`star_${i}`} index={i} onClick={toggleStar} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const mlb = (i: number) => {
|
|
||||||
// console.log("MLB; Number of stars:", props.uncapLevel)
|
|
||||||
return <UncapStar empty={ (props.uncapLevel) ? i >= props.uncapLevel : false } key={`star_${i}`} index={i} onClick={toggleStar} />
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transcendence = (i: number) => {
|
||||||
return (
|
return (
|
||||||
<ul className="UncapIndicator">
|
<UncapStar
|
||||||
{
|
ulb={true}
|
||||||
Array.from(Array(numStars)).map((x, i) => {
|
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||||
if (props.type === 'character' && i > 4) {
|
key={`star_${i}`}
|
||||||
if (props.special)
|
index={i}
|
||||||
return ulb(i)
|
onClick={toggleStar}
|
||||||
else
|
/>
|
||||||
return transcendence(i)
|
|
||||||
} else if (
|
|
||||||
props.special && props.type === 'character' && i == 3 ||
|
|
||||||
props.type === 'character' && i == 4 ||
|
|
||||||
props.type !== 'character' && i > 2) {
|
|
||||||
return flb(i)
|
|
||||||
} else {
|
|
||||||
return mlb(i)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ulb = (i: number) => {
|
||||||
|
return (
|
||||||
|
<UncapStar
|
||||||
|
ulb={true}
|
||||||
|
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||||
|
key={`star_${i}`}
|
||||||
|
index={i}
|
||||||
|
onClick={toggleStar}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const flb = (i: number) => {
|
||||||
|
return (
|
||||||
|
<UncapStar
|
||||||
|
flb={true}
|
||||||
|
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||||
|
key={`star_${i}`}
|
||||||
|
index={i}
|
||||||
|
onClick={toggleStar}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mlb = (i: number) => {
|
||||||
|
// console.log("MLB; Number of stars:", props.uncapLevel)
|
||||||
|
return (
|
||||||
|
<UncapStar
|
||||||
|
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||||
|
key={`star_${i}`}
|
||||||
|
index={i}
|
||||||
|
onClick={toggleStar}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="UncapIndicator">
|
||||||
|
{Array.from(Array(numStars)).map((x, i) => {
|
||||||
|
if (props.type === "character" && i > 4) {
|
||||||
|
if (props.special) return ulb(i)
|
||||||
|
else return transcendence(i)
|
||||||
|
} else if (
|
||||||
|
(props.special && props.type === "character" && i == 3) ||
|
||||||
|
(props.type === "character" && i == 4) ||
|
||||||
|
(props.type !== "character" && i > 2)
|
||||||
|
) {
|
||||||
|
return flb(i)
|
||||||
|
} else {
|
||||||
|
return mlb(i)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UncapIndicator
|
export default UncapIndicator
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import ExtraWeapons from "~components/ExtraWeapons"
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
import { appState } from "~utils/appState"
|
import { appState } from "~utils/appState"
|
||||||
|
|
||||||
|
import type { SearchableObject } from "~types"
|
||||||
|
|
||||||
import "./index.scss"
|
import "./index.scss"
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
|
|
@ -71,10 +73,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
|
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons])
|
||||||
|
|
||||||
// Methods: Adding an object from search
|
// Methods: Adding an object from search
|
||||||
function receiveWeaponFromSearch(
|
function receiveWeaponFromSearch(object: SearchableObject, position: number) {
|
||||||
object: Character | Weapon | Summon,
|
|
||||||
position: number
|
|
||||||
) {
|
|
||||||
const weapon = object as Weapon
|
const weapon = object as Weapon
|
||||||
if (position == 1) appState.party.element = weapon.element
|
if (position == 1) appState.party.element = weapon.element
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,131 +1,145 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from "react"
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from "next/router"
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from "next-i18next"
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames"
|
||||||
|
|
||||||
import SearchModal from '~components/SearchModal'
|
import SearchModal from "~components/SearchModal"
|
||||||
import WeaponModal from '~components/WeaponModal'
|
import WeaponModal from "~components/WeaponModal"
|
||||||
import WeaponHovercard from '~components/WeaponHovercard'
|
import WeaponHovercard from "~components/WeaponHovercard"
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from "~components/UncapIndicator"
|
||||||
import Button from '~components/Button'
|
import Button from "~components/Button"
|
||||||
|
|
||||||
import { ButtonType } from '~utils/enums'
|
import { ButtonType } from "~utils/enums"
|
||||||
|
import type { SearchableObject } from "~types"
|
||||||
|
|
||||||
import PlusIcon from '~public/icons/Add.svg'
|
import PlusIcon from "~public/icons/Add.svg"
|
||||||
import './index.scss'
|
import "./index.scss"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridWeapon: GridWeapon | undefined
|
gridWeapon: GridWeapon | undefined
|
||||||
unitType: 0 | 1
|
unitType: 0 | 1
|
||||||
position: number
|
position: number
|
||||||
editable: boolean
|
editable: boolean
|
||||||
updateObject: (object: Character | Weapon | Summon, position: number) => void
|
updateObject: (object: SearchableObject, position: number) => void
|
||||||
updateUncap: (id: string, position: number, uncap: number) => void
|
updateUncap: (id: string, position: number, uncap: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponUnit = (props: Props) => {
|
const WeaponUnit = (props: Props) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation("common")
|
||||||
|
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState("")
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
const locale =
|
||||||
|
router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
|
||||||
|
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
WeaponUnit: true,
|
WeaponUnit: true,
|
||||||
'mainhand': props.unitType == 0,
|
mainhand: props.unitType == 0,
|
||||||
'grid': props.unitType == 1,
|
grid: props.unitType == 1,
|
||||||
'editable': props.editable,
|
editable: props.editable,
|
||||||
'filled': (props.gridWeapon !== undefined)
|
filled: props.gridWeapon !== undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridWeapon = props.gridWeapon
|
const gridWeapon = props.gridWeapon
|
||||||
const weapon = gridWeapon?.object
|
const weapon = gridWeapon?.object
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateImageUrl()
|
generateImageUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateImageUrl() {
|
function generateImageUrl() {
|
||||||
let imgSrc = ""
|
let imgSrc = ""
|
||||||
if (props.gridWeapon) {
|
if (props.gridWeapon) {
|
||||||
const weapon = props.gridWeapon.object!
|
const weapon = props.gridWeapon.object!
|
||||||
|
|
||||||
if (props.unitType == 0) {
|
if (props.unitType == 0) {
|
||||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
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`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
||||||
else
|
else
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}.jpg`
|
||||||
} else {
|
} else {
|
||||||
if (props.gridWeapon.object.element == 0 && props.gridWeapon.element)
|
if (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`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${props.gridWeapon.element}.jpg`
|
||||||
else
|
else
|
||||||
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setImageUrl(imgSrc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function passUncapData(uncap: number) {
|
setImageUrl(imgSrc)
|
||||||
if (props.gridWeapon)
|
}
|
||||||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
|
||||||
}
|
|
||||||
|
|
||||||
function canBeModified(gridWeapon: GridWeapon) {
|
function passUncapData(uncap: number) {
|
||||||
const weapon = gridWeapon.object
|
if (props.gridWeapon)
|
||||||
|
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
||||||
|
}
|
||||||
|
|
||||||
return weapon.ax > 0 ||
|
function canBeModified(gridWeapon: GridWeapon) {
|
||||||
(weapon.series) && [2, 3, 17, 22, 24].includes(weapon.series)
|
const weapon = gridWeapon.object
|
||||||
}
|
|
||||||
|
|
||||||
const image = (
|
return (
|
||||||
<div className="WeaponImage">
|
weapon.ax > 0 ||
|
||||||
<img alt={weapon?.name.en} className="grid_image" src={imageUrl} />
|
(weapon.series && [2, 3, 17, 22, 24].includes(weapon.series))
|
||||||
{ (props.editable) ? <span className='icon'><PlusIcon /></span> : '' }
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const editableImage = (
|
const image = (
|
||||||
<SearchModal
|
<div className="WeaponImage">
|
||||||
placeholderText={t('search.placeholders.weapon')}
|
<img alt={weapon?.name.en} className="grid_image" src={imageUrl} />
|
||||||
fromPosition={props.position}
|
{props.editable ? (
|
||||||
object="weapons"
|
<span className="icon">
|
||||||
send={props.updateObject}>
|
<PlusIcon />
|
||||||
{image}
|
</span>
|
||||||
</SearchModal>
|
) : (
|
||||||
)
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const unitContent = (
|
const editableImage = (
|
||||||
<div className={classes}>
|
<SearchModal
|
||||||
{ (props.editable && gridWeapon && canBeModified(gridWeapon)) ?
|
placeholderText={t("search.placeholders.weapon")}
|
||||||
<WeaponModal gridWeapon={gridWeapon}>
|
fromPosition={props.position}
|
||||||
<div>
|
object="weapons"
|
||||||
<Button icon="settings" type={ButtonType.IconOnly}/>
|
send={props.updateObject}
|
||||||
</div>
|
>
|
||||||
</WeaponModal>: '' }
|
{image}
|
||||||
{ (props.editable) ? editableImage : image }
|
</SearchModal>
|
||||||
{ (gridWeapon) ?
|
)
|
||||||
<UncapIndicator
|
|
||||||
type="weapon"
|
|
||||||
ulb={gridWeapon.object.uncap.ulb || false}
|
|
||||||
flb={gridWeapon.object.uncap.flb || false}
|
|
||||||
uncapLevel={gridWeapon.uncap_level}
|
|
||||||
updateUncap={passUncapData}
|
|
||||||
special={false}
|
|
||||||
/> : ''
|
|
||||||
}
|
|
||||||
<h3 className="WeaponName">{weapon?.name[locale]}</h3>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const withHovercard = (
|
const unitContent = (
|
||||||
<WeaponHovercard gridWeapon={gridWeapon!}>
|
<div className={classes}>
|
||||||
{unitContent}
|
{props.editable && gridWeapon && canBeModified(gridWeapon) ? (
|
||||||
</WeaponHovercard>
|
<WeaponModal gridWeapon={gridWeapon}>
|
||||||
)
|
<div>
|
||||||
|
<Button icon="settings" type={ButtonType.IconOnly} />
|
||||||
|
</div>
|
||||||
|
</WeaponModal>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
{props.editable ? editableImage : image}
|
||||||
|
{gridWeapon ? (
|
||||||
|
<UncapIndicator
|
||||||
|
type="weapon"
|
||||||
|
ulb={gridWeapon.object.uncap.ulb || false}
|
||||||
|
flb={gridWeapon.object.uncap.flb || false}
|
||||||
|
uncapLevel={gridWeapon.uncap_level}
|
||||||
|
updateUncap={passUncapData}
|
||||||
|
special={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<h3 className="WeaponName">{weapon?.name[locale]}</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (gridWeapon && !props.editable) ? withHovercard : unitContent
|
const withHovercard = (
|
||||||
|
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridWeapon && !props.editable ? withHovercard : unitContent
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponUnit
|
export default WeaponUnit
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import React from "react"
|
import React, { useEffect } from "react"
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||||
|
|
||||||
import Party from "~components/Party"
|
import Party from "~components/Party"
|
||||||
|
|
||||||
|
import { appState } from "~utils/appState"
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
jobs: Job[]
|
||||||
|
jobSkills: JobSkill[]
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +22,16 @@ const NewRoute: React.FC<Props> = (props: Props) => {
|
||||||
window.history.replaceState(null, `Grid Tool`, `${path}`)
|
window.history.replaceState(null, `Grid Tool`, `${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
persistStaticData()
|
||||||
|
}, [persistStaticData])
|
||||||
|
|
||||||
|
function persistStaticData() {
|
||||||
|
appState.raids = props.raids
|
||||||
|
appState.jobs = props.jobs
|
||||||
|
appState.jobSkills = props.jobSkills
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="Content">
|
<div id="Content">
|
||||||
<Party new={true} raids={props.sortedRaids} pushHistory={callback} />
|
<Party new={true} raids={props.sortedRaids} pushHistory={callback} />
|
||||||
|
|
@ -51,8 +65,17 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
.getAll({ params: headers })
|
.getAll({ params: headers })
|
||||||
.then((response) => organizeRaids(response.data.map((r: any) => r.raid)))
|
.then((response) => organizeRaids(response.data.map((r: any) => r.raid)))
|
||||||
|
|
||||||
|
let jobs = await api.endpoints.jobs
|
||||||
|
.getAll({ params: headers })
|
||||||
|
.then((response) => { return response.data })
|
||||||
|
|
||||||
|
let jobSkills = await api.allSkills(headers)
|
||||||
|
.then((response) => { return response.data })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
jobs: jobs,
|
||||||
|
jobSkills: jobSkills,
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
...(await serverSideTranslations(locale, ["common"])),
|
...(await serverSideTranslations(locale, ["common"])),
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,33 @@
|
||||||
import React from "react"
|
import React, { useEffect } from "react"
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||||
|
|
||||||
import Party from "~components/Party"
|
import Party from "~components/Party"
|
||||||
|
|
||||||
|
import { appState } from "~utils/appState"
|
||||||
import api from "~utils/api"
|
import api from "~utils/api"
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
party: Party
|
party: Party
|
||||||
|
jobs: Job[]
|
||||||
|
jobSkills: JobSkill[]
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
const PartyRoute: React.FC<Props> = (props: Props) => {
|
const PartyRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
useEffect(() => {
|
||||||
|
persistStaticData()
|
||||||
|
}, [persistStaticData])
|
||||||
|
|
||||||
|
function persistStaticData() {
|
||||||
|
appState.raids = props.raids
|
||||||
|
appState.jobs = props.jobs
|
||||||
|
appState.jobSkills = props.jobSkills
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="Content">
|
<div id="Content">
|
||||||
<Party team={props.party} raids={props.sortedRaids} />
|
<Party team={props.party} raids={props.sortedRaids} />
|
||||||
|
|
@ -48,6 +61,16 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
.getAll()
|
.getAll()
|
||||||
.then((response) => organizeRaids(response.data.map((r: any) => r.raid)))
|
.then((response) => organizeRaids(response.data.map((r: any) => r.raid)))
|
||||||
|
|
||||||
|
let jobs = await api.endpoints.jobs
|
||||||
|
.getAll({ params: headers })
|
||||||
|
.then((response) => {
|
||||||
|
return response.data
|
||||||
|
})
|
||||||
|
|
||||||
|
let jobSkills = await api.allSkills(headers).then((response) => {
|
||||||
|
return response.data
|
||||||
|
})
|
||||||
|
|
||||||
let party: Party | null = null
|
let party: Party | null = null
|
||||||
if (query.party) {
|
if (query.party) {
|
||||||
let response = await api.endpoints.parties.getOne({ id: query.party, params: headers })
|
let response = await api.endpoints.parties.getOne({ id: query.party, params: headers })
|
||||||
|
|
@ -59,6 +82,8 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
party: party,
|
party: party,
|
||||||
|
jobs: jobs,
|
||||||
|
jobSkills: jobSkills,
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
...(await serverSideTranslations(locale, ["common"])),
|
...(await serverSideTranslations(locale, ["common"])),
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,8 @@
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"weapon": "Search for a weapon...",
|
"weapon": "Search for a weapon...",
|
||||||
"summon": "Search for a summon...",
|
"summon": "Search for a summon...",
|
||||||
"character": "Search for a character..."
|
"character": "Search for a character...",
|
||||||
|
"job_skill": "Search job skills..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
|
|
@ -240,6 +241,19 @@
|
||||||
"loading": "Loading teams...",
|
"loading": "Loading teams...",
|
||||||
"not_found": "No teams found"
|
"not_found": "No teams found"
|
||||||
},
|
},
|
||||||
|
"job_skills": {
|
||||||
|
"all": "All skills",
|
||||||
|
"buffing": "Buffing",
|
||||||
|
"debuffing": "Debuffing",
|
||||||
|
"damaging": "Damaging",
|
||||||
|
"healing": "Healing",
|
||||||
|
"emp": "Extended Mastery",
|
||||||
|
"base": "Base Skills",
|
||||||
|
"state": {
|
||||||
|
"selectable": "Select a skill",
|
||||||
|
"no_skill": "No skill"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extra_weapons": "Additional Weapons",
|
"extra_weapons": "Additional Weapons",
|
||||||
"coming_soon": "Coming Soon",
|
"coming_soon": "Coming Soon",
|
||||||
"no_title": "Untitled",
|
"no_title": "Untitled",
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,8 @@
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"weapon": "武器を検索...",
|
"weapon": "武器を検索...",
|
||||||
"summon": "召喚石を検索...",
|
"summon": "召喚石を検索...",
|
||||||
"character": "キャラを検索..."
|
"character": "キャラを検索...",
|
||||||
|
"job_skill": "ジョブのスキルを検索..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
|
|
@ -241,6 +242,19 @@
|
||||||
"loading": "ロード中...",
|
"loading": "ロード中...",
|
||||||
"not_found": "編成は見つかりませんでした"
|
"not_found": "編成は見つかりませんでした"
|
||||||
},
|
},
|
||||||
|
"job_skills": {
|
||||||
|
"all": "全てのアビリティ",
|
||||||
|
"buffing": "強化アビリティ",
|
||||||
|
"debuffing": "弱体アビリティ",
|
||||||
|
"damaging": "ダメージアビリティ",
|
||||||
|
"healing": "回復アビリティ",
|
||||||
|
"emp": "リミットアビリティ",
|
||||||
|
"base": "ベースアビリティ",
|
||||||
|
"state": {
|
||||||
|
"selectable": "アビリティを選択",
|
||||||
|
"no_skill": "設定されていません"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extra_weapons": "Additional<br/>Weapons",
|
"extra_weapons": "Additional<br/>Weapons",
|
||||||
"coming_soon": "開発中",
|
"coming_soon": "開発中",
|
||||||
"no_title": "無題",
|
"no_title": "無題",
|
||||||
|
|
|
||||||
31
types/Job.d.ts
vendored
31
types/Job.d.ts
vendored
|
|
@ -1,15 +1,16 @@
|
||||||
interface Job {
|
interface Job {
|
||||||
id: string
|
id: string
|
||||||
row: string
|
row: string
|
||||||
ml: boolean
|
ml: boolean
|
||||||
order: number
|
order: number
|
||||||
name: {
|
name: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
en: string
|
en: string
|
||||||
ja: string
|
ja: string
|
||||||
}
|
}
|
||||||
proficiency: {
|
proficiency: {
|
||||||
proficiency1: number
|
proficiency1: number
|
||||||
proficiency2: number
|
proficiency2: number
|
||||||
}
|
}
|
||||||
}
|
base_job?: Job
|
||||||
|
}
|
||||||
|
|
|
||||||
16
types/JobSkill.d.ts
vendored
Normal file
16
types/JobSkill.d.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
interface JobSkill {
|
||||||
|
id: string
|
||||||
|
job: Job
|
||||||
|
name: {
|
||||||
|
[key: string]: string
|
||||||
|
en: string
|
||||||
|
ja: string
|
||||||
|
}
|
||||||
|
slug: string
|
||||||
|
color: number
|
||||||
|
main: boolean
|
||||||
|
base: boolean
|
||||||
|
sub: boolean
|
||||||
|
emp: boolean
|
||||||
|
order: number
|
||||||
|
}
|
||||||
10
types/Party.d.ts
vendored
10
types/Party.d.ts
vendored
|
|
@ -1,8 +1,18 @@
|
||||||
|
type JobSkillObject = {
|
||||||
|
[key: number]: JobSkill | undefined
|
||||||
|
0: JobSkill | undefined
|
||||||
|
1: JobSkill | undefined
|
||||||
|
2: JobSkill | undefined
|
||||||
|
3: JobSkill | undefined
|
||||||
|
}
|
||||||
|
|
||||||
interface Party {
|
interface Party {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
raid: Raid
|
raid: Raid
|
||||||
|
job: Job
|
||||||
|
job_skills: JobSkillObject
|
||||||
shortcode: string
|
shortcode: string
|
||||||
extra: boolean
|
extra: boolean
|
||||||
favorited: boolean
|
favorited: boolean
|
||||||
|
|
|
||||||
9
types/index.d.ts
vendored
Normal file
9
types/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export type SearchableObject = Character | Weapon | Summon | JobSkill
|
||||||
|
export type SearchableObjectArray = (Character | Weapon | Summon | JobSkill)[]
|
||||||
|
export type JobSkillObject = {
|
||||||
|
[key: number]: JobSkill | undefined
|
||||||
|
0: JobSkill | undefined
|
||||||
|
1: JobSkill | undefined
|
||||||
|
2: JobSkill | undefined
|
||||||
|
3: JobSkill | undefined
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ class Api {
|
||||||
url: string
|
url: string
|
||||||
endpoints: { [key: string]: EndpointMap }
|
endpoints: { [key: string]: EndpointMap }
|
||||||
|
|
||||||
constructor({url}: {url: string}) {
|
constructor({ url }: { url: string }) {
|
||||||
this.url = url
|
this.url = url
|
||||||
this.endpoints = {}
|
this.endpoints = {}
|
||||||
}
|
}
|
||||||
|
|
@ -56,13 +56,14 @@ class Api {
|
||||||
return axios.post(`${ oauthUrl }/token`, object)
|
return axios.post(`${ oauthUrl }/token`, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
search({ object, query, filters, locale = "en", page = 0 }:
|
search({ object, query, job, filters, locale = "en", page = 0 }:
|
||||||
{ object: string, query: string, filters?: { [key: string]: number[] }, locale?: string, page?: number }) {
|
{ object: string, query: string, job?: string, filters?: { [key: string]: number[] }, locale?: string, page?: number }) {
|
||||||
const resourceUrl = `${this.url}/${name}`
|
const resourceUrl = `${this.url}/${name}`
|
||||||
return axios.post(`${resourceUrl}search/${object}`, {
|
return axios.post(`${resourceUrl}search/${object}`, {
|
||||||
search: {
|
search: {
|
||||||
query: query,
|
query: query,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
|
job: job,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
page: page
|
page: page
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +93,22 @@ class Api {
|
||||||
const resourceUrl = `${this.url}/characters/resolve`
|
const resourceUrl = `${this.url}/characters/resolve`
|
||||||
return axios.post(resourceUrl, body, { headers: params })
|
return axios.post(resourceUrl, body, { headers: params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateJob({ partyId, params }: { partyId: string, params?: {} }) {
|
||||||
|
const resourceUrl = `${this.url}/parties/${partyId}/jobs`
|
||||||
|
return axios.put(resourceUrl, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJobSkills({ partyId, params }: { partyId: string, params?: {} }) {
|
||||||
|
const resourceUrl = `${this.url}/parties/${partyId}/job_skills`
|
||||||
|
return axios.put(resourceUrl, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
allSkills(params: {}) {
|
||||||
|
const resourceUrl = `${this.url}/jobs/skills`
|
||||||
|
return axios.get(resourceUrl, params)
|
||||||
|
}
|
||||||
|
|
||||||
savedTeams(params: {}) {
|
savedTeams(params: {}) {
|
||||||
const resourceUrl = `${this.url}/parties/favorites`
|
const resourceUrl = `${this.url}/parties/favorites`
|
||||||
return axios.get(resourceUrl, params)
|
return axios.get(resourceUrl, params)
|
||||||
|
|
@ -127,15 +144,15 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
||||||
api.createEntity( { name: 'users' })
|
api.createEntity({ name: 'users' })
|
||||||
api.createEntity( { name: 'parties' })
|
api.createEntity({ name: 'parties' })
|
||||||
api.createEntity( { name: 'grid_weapons' })
|
api.createEntity({ name: 'grid_weapons' })
|
||||||
api.createEntity( { name: 'characters' })
|
api.createEntity({ name: 'characters' })
|
||||||
api.createEntity( { name: 'weapons' })
|
api.createEntity({ name: 'weapons' })
|
||||||
api.createEntity( { name: 'summons' })
|
api.createEntity({ name: 'summons' })
|
||||||
api.createEntity( { name: 'jobs' })
|
api.createEntity({ name: 'jobs' })
|
||||||
api.createEntity( { name: 'raids' })
|
api.createEntity({ name: 'raids' })
|
||||||
api.createEntity( { name: 'weapon_keys' })
|
api.createEntity({ name: 'weapon_keys' })
|
||||||
api.createEntity( { name: 'favorites' })
|
api.createEntity({ name: 'favorites' })
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { proxy } from "valtio"
|
import { proxy } from "valtio"
|
||||||
|
import { JobSkillObject } from "~types"
|
||||||
|
|
||||||
const emptyJob: Job = {
|
const emptyJob: Job = {
|
||||||
id: "-1",
|
id: "-1",
|
||||||
|
|
@ -25,6 +26,7 @@ interface AppState {
|
||||||
name: string | undefined
|
name: string | undefined
|
||||||
description: string | undefined
|
description: string | undefined
|
||||||
job: Job
|
job: Job
|
||||||
|
jobSkills: JobSkillObject
|
||||||
raid: Raid | undefined
|
raid: Raid | undefined
|
||||||
element: number
|
element: number
|
||||||
extra: boolean
|
extra: boolean
|
||||||
|
|
@ -53,6 +55,8 @@ interface AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
|
jobs: Job[]
|
||||||
|
jobSkills: JobSkill[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialAppState: AppState = {
|
export const initialAppState: AppState = {
|
||||||
|
|
@ -63,6 +67,12 @@ export const initialAppState: AppState = {
|
||||||
name: undefined,
|
name: undefined,
|
||||||
description: undefined,
|
description: undefined,
|
||||||
job: emptyJob,
|
job: emptyJob,
|
||||||
|
jobSkills: {
|
||||||
|
0: undefined,
|
||||||
|
1: undefined,
|
||||||
|
2: undefined,
|
||||||
|
3: undefined,
|
||||||
|
},
|
||||||
raid: undefined,
|
raid: undefined,
|
||||||
element: 0,
|
element: 0,
|
||||||
extra: false,
|
extra: false,
|
||||||
|
|
@ -91,6 +101,8 @@ export const initialAppState: AppState = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
raids: [],
|
raids: [],
|
||||||
|
jobs: [],
|
||||||
|
jobSkills: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appState = proxy(initialAppState)
|
export const appState = proxy(initialAppState)
|
||||||
|
|
|
||||||
91
utils/skillGroups.tsx
Normal file
91
utils/skillGroups.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
export interface SkillGroup {
|
||||||
|
id: number
|
||||||
|
name: {
|
||||||
|
[key: string]: string
|
||||||
|
en: string
|
||||||
|
ja: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const skillClassification: SkillGroup[] = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: {
|
||||||
|
en: "Buffing",
|
||||||
|
ja: "強化アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: {
|
||||||
|
en: "Debuffing",
|
||||||
|
ja: "弱体アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: {
|
||||||
|
en: "Damaging",
|
||||||
|
ja: "ダメージアビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: {
|
||||||
|
en: "Healing",
|
||||||
|
ja: "回復アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: {
|
||||||
|
en: "Field",
|
||||||
|
ja: "フィールドアビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const skillGroups: SkillGroup[] = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: {
|
||||||
|
en: "Buffing",
|
||||||
|
ja: "強化アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: {
|
||||||
|
en: "Debuffing",
|
||||||
|
ja: "弱体アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: {
|
||||||
|
en: "Damaging",
|
||||||
|
ja: "ダメージアビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: {
|
||||||
|
en: "Healing",
|
||||||
|
ja: "回復アビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: {
|
||||||
|
en: "Extended Mastery",
|
||||||
|
ja: "リミットアビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: {
|
||||||
|
en: "Base",
|
||||||
|
ja: "ベースアビリティ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue