Compare commits

...

41 commits

Author SHA1 Message Date
31b590a542 WIP checkpoint
abandoning this though
2023-07-07 22:07:41 -07:00
973e7f34da Fix typings and output with remixes 2023-07-07 14:32:03 -07:00
d0535d6031 Fix bugs relating to [party] 2023-07-07 07:00:44 -07:00
b2f64f1d78 Fix transformers and types
Dozens of tiny errors from me freehanding it
2023-07-07 06:47:41 -07:00
d42927623e Update all files with new object structure 2023-07-07 05:43:02 -07:00
500b7ffbbf Add the last transformers 2023-07-06 23:57:25 -07:00
f73787c23d Add transformer for Job classes 2023-07-06 23:48:32 -07:00
fe32a49bc4 Add transformer for GridSummon 2023-07-06 23:37:10 -07:00
8ef2bf76e3 Add transformer for GridWeapon 2023-07-06 23:33:51 -07:00
7cd87bd4a7 Add transformer for GridCharacter 2023-07-06 23:20:27 -07:00
511d7ee0ec Fix comments 2023-07-06 23:20:09 -07:00
2838622335 Add transformers for core entities
canonical Characters, Weapons and Summons
2023-07-06 22:59:45 -07:00
587153a9e2 Update User.d.ts 2023-07-06 22:55:58 -07:00
7224ae8585 Make signatures consistent 2023-07-06 22:55:10 -07:00
43ccb464b1 Merge branch 'staging' into transformers 2023-07-06 22:35:56 -07:00
686d0d0642 Disable tab pages 2023-07-06 22:26:43 -07:00
af9064a356 Some fixes for scrollable dialogs on mobile
This is 100% not going to scale to devices that are not my iPhone 14 Pro Max, but I can't get env variables working in CSS and something is better than nothing for right now.
2023-07-06 19:22:11 -07:00
2049ad4cf7 Put viewport meta tag in _app 2023-07-06 19:21:35 -07:00
14994bfbbd Fix TableField components on mobile 2023-07-06 18:37:44 -07:00
8dbc6c1c6c Small fix for some modals on mobile
This fixes the slide up animation and the end point so that modals are actually visible on mobile. Ones that scroll still don't work great.
2023-07-06 18:37:30 -07:00
11577a6b61 Ensure name modification works right
Needed a null check? for some reason?
2023-07-06 17:08:37 -07:00
abd98d27c9 Add placeholder to party description 2023-07-06 17:07:05 -07:00
17113e2ad9 Add placeholder extension 2023-07-06 16:58:37 -07:00
5e0bda987d Show localized placeholder for team name 2023-07-06 16:58:29 -07:00
da1acb1463 Allow translation of Heading icons 2023-07-06 16:58:22 -07:00
93e4fd74fd Add toolbar localizations 2023-07-06 16:58:07 -07:00
bed7d0d408
Update transcendence components to work with CSS modules (#350)
* Update transcendence components to use CSS modules

* Fix summon transcendence

Summon transcendence was doing something wonky. This adapts the updateUncap endpoint method to make it a little bit clearer whats going on.
2023-07-06 15:56:23 -07:00
a70c09b373 Fix styles 2023-07-06 03:33:07 -07:00
5b42ca862e Remove duplicate binding 2023-07-06 03:08:12 -07:00
78ae6f2fd1
Merge branch 'main' into staging 2023-07-06 03:06:39 -07:00
8ea2f97aac Fix icon path 2023-07-06 03:05:31 -07:00
8f7670c07b
Merge branch 'main' into staging 2023-07-06 02:55:46 -07:00
83bebdb0c2
Tiptap updates (#343)
* Reinstantiate editor on changes

We can't dynamically update the content, so we have to recreate the Editor whenever something changes (page loads and updates)

* Fix import

@tiptap/core is different than @tiptap/react, who knew

* Added several Tiptap components

* Added a Remix icon that isn't in remixicon-react

* Add colors for highlights

* Add ToolbarButton component

This is to standardize adding Toolbar icons so it wasn't a miserable mess in the Editor file

* Add extensions and implement ToolbarButton

* Remove unused code

* Use party prop and add keys

We always want to use the party in props until the transformer work is done and our source of truth is more reliable.

Also, we are using keys to ensure that the component reloads on new page.

* Component cleanup

* Always use props.party

* Ensure content gets reset when edits are committed

Here, we do some tactical bandaid fixes to ensure that when the user saves data to the server, the editor will show the freshest data in both editable and read-only mode.

In the Editor, its as easy as calling the setContent command in a useEffect hook when the content changes.

In the party, we are saving party in a state and passing it down to the components via props. This is because the party prop we get from pages is only from the first time the server loaded data, so any edits are not reflected. The app state should have the latest updates, but due to reasons I don't completely understand, it is showing the old state first and then the one we want, causing the Editor to get stuck on old data.

By storing the party in a state, we can populate the state from the server when the component mounts, then update it whenever the user saves data since all party data is saved in that component.

* Fix build errors
2023-07-06 02:51:01 -07:00
7c814610b9 Create PartyTransformer
Transforms data into Party objects and back again.

We also created PartyParams to send data back to the API in a uniform way.

We organized the resulting object more than we have in the past since we can do what we want now.

Lastly, we removed characters, weapons and summons from this object. We will probably make a new Grid object and reference that here instead.
2023-07-06 00:04:52 -07:00
881ed31dd1 Create UserTransformer
Transforms data into User objects and back again.

We added the UserParams interface to handle sending data back to the API in a uniform manner.

We also modified the User object to store the user's language and theme for simplicity's sake, since the app state wants this information today.
2023-07-06 00:03:21 -07:00
f5ee806f8b Create RaidTransformer
Transforms data into Raid objects. Also, updated Raid type to use GranblueElement.
2023-07-06 00:02:11 -07:00
6ab2c2488d Create RaidGroupTransformer
Transforms data into RaidGroup objects
2023-07-06 00:01:36 -07:00
cb4fd491ac Create ElementTransformer
Transforms elements from numbers into objects and back again
2023-07-06 00:01:17 -07:00
9adcd50519 Fix background-color on CharacterRep 2023-07-05 22:35:54 -07:00
955cd14762 Override peer dependencies for tiptap mentions
They haven't fixed the suggestion plugin, so we have to use a beta version
2023-07-05 21:43:03 -07:00
15a32d56bb
Rich text editor and support for tagging objects (#340)
* Preliminary work around making an Element type

* Disabled Youtube code for now

* Clean description with DOMPurify

* Update GranblueElement with slug

* Add new api endpoint for searching all resources

* Add new variables and themes

* Remove fixed height on html tag for now

* Update README.md

We renamed the folders for character images from `chara-` to `character-`

* Add no results string

* Add tiptap and associated packages

* Update .gitignore

* Update components that use character images

* Add Editor component

This commit adds the bulk of the code for our new rich-text editor. The Editor component will be used to edit and display rich text via Tiptap.

* Add mention components

This adds the code required for us to mention objects in rich text fields like team descriptions.

The mentionSuggestion util fetches data from the server and serves it to MentionList for the user to select, then inserts it into the Editor as a token.

* Implements Editor in edit team and team footer

This implements the Editor component in EditPartyModal and PartyFooter. In PartyFooter, it is read-only.

* Remove min-width on tokens

* Add rudimentary conversion for old descriptions

Old descriptions just translate as a blob of text, so we try to insert some paragraphs and newlines to keep things presentable and lessen the load if users decide to update

* Add support for displaying jobs in MentionList

* Handle numbers and value=0 better

* Keep description reactive

This shouldn't work? The snapshot should be the reactive one? I don't fucking know

* Send locale to api with search query

* Delete getLocale.tsx

We didn't actually use this

* Fix build errors
2023-07-05 21:19:34 -07:00
137 changed files with 2326 additions and 1461 deletions

View file

@ -3,12 +3,13 @@ import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import classNames from 'classnames' import classNames from 'classnames'
import * as ElementTransformer from '~transformers/ElementTransformer'
import * as ToggleGroup from '@radix-ui/react-toggle-group' import * as ToggleGroup from '@radix-ui/react-toggle-group'
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props { interface Props {
currentElement: number currentElement: GranblueElement
sendValue: (value: number) => void sendValue: (value: GranblueElement) => void
} }
const ElementToggle = ({ currentElement, sendValue, ...props }: Props) => { const ElementToggle = ({ currentElement, sendValue, ...props }: Props) => {
@ -24,7 +25,7 @@ const ElementToggle = ({ currentElement, sendValue, ...props }: Props) => {
// Methods: Handlers // Methods: Handlers
const handleElementChange = (value: string) => { const handleElementChange = (value: string) => {
const newElement = parseInt(value) const newElement = ElementTransformer.toObject(parseInt(value))
setElement(newElement) setElement(newElement)
sendValue(newElement) sendValue(newElement)
} }

View file

@ -8,6 +8,8 @@ import 'fix-date'
import { accountState } from '~utils/accountState' import { accountState } from '~utils/accountState'
import { formatTimeAgo } from '~utils/timeAgo' import { formatTimeAgo } from '~utils/timeAgo'
import { ElementMap } from '~utils/elements'
import { mapToGridArray } from '~utils/mapToGridArray'
import Button from '~components/common/Button' import Button from '~components/common/Button'
@ -19,9 +21,12 @@ interface Props {
shortcode: string shortcode: string
id: string id: string
name: string name: string
raid: Raid raid: Raid | null
grid: GridWeapon[] weapons: {
user?: User mainWeapon: GridWeapon | null
allWeapons: GridArray<GridWeapon> | null
} | null
user: User | null
fullAuto: boolean fullAuto: boolean
autoGuard: boolean autoGuard: boolean
favorited: boolean favorited: boolean
@ -69,27 +74,19 @@ const GridRep = (props: Props) => {
}) })
useEffect(() => { useEffect(() => {
const newWeapons = Array(numWeapons) if (props.weapons && props.weapons.mainWeapon) {
const gridWeapons = Array(numWeapons) setMainhand(props.weapons.mainWeapon?.object)
let foundMainhand = false
for (const [key, value] of Object.entries(props.grid)) {
if (value.position == -1) {
setMainhand(value.object)
foundMainhand = true
} else if (!value.mainhand && value.position != null) {
newWeapons[value.position] = value.object
gridWeapons[value.position] = value
}
} }
if (!foundMainhand) { if (props.weapons && props.weapons.allWeapons) {
setMainhand(undefined) setWeapons(
mapToGridArray(
Object.values(props.weapons.allWeapons).map((w) => w?.object)
)
)
setGrid(props.weapons.allWeapons)
} }
}, [props.weapons])
setWeapons(newWeapons)
setGrid(gridWeapons)
}, [props.grid])
function navigate() { function navigate() {
props.onClick(props.shortcode) props.onClick(props.shortcode)
@ -99,22 +96,16 @@ const GridRep = (props: Props) => {
let url = '' let url = ''
if (mainhand) { if (mainhand) {
const weapon = Object.values(props.grid).find( const weapon = props.weapons?.mainWeapon
(w) => w && w.object.id === mainhand.id
)
if (mainhand.element == 0 && weapon && weapon.element) { if (mainhand.element === ElementMap.null && weapon && weapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}_${weapon.element}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblueId}_${weapon.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblue_id}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.granblueId}.jpg`
} }
} }
return mainhand && props.grid[0] ? ( return mainhand && <img alt={mainhand.name[locale]} src={url} />
<img alt={mainhand.name[locale]} src={url} />
) : (
''
)
} }
function generateGridImage(position: number) { function generateGridImage(position: number) {
@ -124,17 +115,17 @@ const GridRep = (props: Props) => {
const gridWeapon = grid[position] const gridWeapon = grid[position]
if (weapon && gridWeapon) { if (weapon && gridWeapon) {
if (weapon.element == 0 && gridWeapon.element) { if (weapon.element === ElementMap.null && gridWeapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}_${gridWeapon.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}.jpg`
} }
} }
return weapons[position] ? ( return (
<img alt={weapons[position]?.name[locale]} src={url} /> weapons[position] && (
) : ( <img alt={weapons[position]?.name[locale]} src={url} />
'' )
) )
} }
@ -209,30 +200,28 @@ const GridRep = (props: Props) => {
{` · ${t('party.details.labels.full_auto')}`} {` · ${t('party.details.labels.full_auto')}`}
</span> </span>
)} )}
{props.raid && props.raid.group.extra && ( {props.raid && props.raid.group?.extra && (
<span className={styles.extra}>{` · EX`}</span> <span className={styles.extra}>{` · EX`}</span>
)} )}
</div> </div>
</div> </div>
{account.authorized && {account.authorized &&
((props.user && account.user && account.user.id !== props.user.id) || ((props.user && account.user && account.user.id !== props.user.id) ||
!props.user) ? ( !props.user) && (
<Link href="#"> <Link href="#">
<Button <Button
className={classNames({ className={classNames({
save: true, save: true,
saved: props.favorited, saved: props.favorited,
})} })}
leftAccessoryIcon={<SaveIcon className="stroke" />} leftAccessoryIcon={<SaveIcon className="stroke" />}
active={props.favorited} active={props.favorited}
bound={true} bound={true}
size="small" size="small"
onClick={sendSaveData} onClick={sendSaveData}
/> />
</Link> </Link>
) : ( )}
''
)}
</div> </div>
<div className={styles.attributed}> <div className={styles.attributed}>
{attribution()} {attribution()}

View file

@ -2,6 +2,7 @@ import { useRouter } from 'next/router'
import UncapIndicator from '~components/uncap/UncapIndicator' import UncapIndicator from '~components/uncap/UncapIndicator'
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon' import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -34,10 +35,11 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
const overlay = () => { const overlay = () => {
if (type === 'character') { if (type === 'character') {
const gridCharacter = gridObject as GridCharacter const gridCharacter = gridObject as GridCharacter
if (gridCharacter.perpetuity) return <i className={styles.perpetuity} /> if (gridCharacter.mastery.perpetuity)
return <i className={styles.perpetuity} />
} else if (type === 'summon') { } else if (type === 'summon') {
const gridSummon = gridObject as GridSummon const gridSummon = gridObject as GridSummon
if (gridSummon.quick_summon) return <i className={styles.quickSummon} /> if (gridSummon.quickSummon) return <i className={styles.quickSummon} />
} }
} }
@ -47,11 +49,11 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = '01' let suffix = '01'
if (gridCharacter.uncap_level == 6) suffix = '04' if (gridCharacter.uncapLevel == 6) suffix = '04'
else if (gridCharacter.uncap_level == 5) suffix = '03' else if (gridCharacter.uncapLevel == 5) suffix = '03'
else if (gridCharacter.uncap_level > 2) suffix = '02' else if (gridCharacter.uncapLevel > 2) suffix = '02'
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_${suffix}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblueId}_${suffix}.jpg`
} }
const summonImage = () => { const summonImage = () => {
@ -71,29 +73,29 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
let suffix = '' let suffix = ''
if ( if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblueId.toString()) != -1 &&
gridSummon.uncap_level == 5 gridSummon.uncapLevel == 5
) { ) {
suffix = '_02' suffix = '_02'
} else if ( } else if (
gridSummon.object.uncap.xlb && gridSummon.object.uncap.xlb &&
gridSummon.transcendence_step > 0 gridSummon.transcendenceStep > 0
) { ) {
suffix = '_03' suffix = '_03'
} }
// Generate the correct source for the summon // Generate the correct source for the summon
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblueId}${suffix}.jpg`
} }
const weaponImage = () => { const weaponImage = () => {
const gridWeapon = gridObject as GridWeapon const gridWeapon = gridObject as GridWeapon
const weapon = object as Weapon const weapon = object as Weapon
if (gridWeapon.object.element == 0 && gridWeapon.element) if (gridWeapon.object.element === ElementMap.null && gridWeapon.element)
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}_${gridWeapon.element}.jpg`
else else
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}.jpg`
} }
const image = () => { const image = () => {
@ -118,7 +120,7 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
</div> </div>
<div className={styles.subInfo}> <div className={styles.subInfo}>
<div className={styles.icons}> <div className={styles.icons}>
<WeaponLabelIcon labelType={Element[object.element]} /> <WeaponLabelIcon labelType={object.element.slug} />
{'proficiency' in object && Array.isArray(object.proficiency) && ( {'proficiency' in object && Array.isArray(object.proficiency) && (
<WeaponLabelIcon labelType={Proficiency[object.proficiency[0]]} /> <WeaponLabelIcon labelType={Proficiency[object.proficiency[0]]} />
)} )}
@ -136,9 +138,7 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
ulb={object.uncap.ulb || false} ulb={object.uncap.ulb || false}
flb={object.uncap.flb || false} flb={object.uncap.flb || false}
transcendenceStage={ transcendenceStage={
'transcendence_step' in gridObject 'transcendenceStep' in gridObject ? gridObject.transcendenceStep : 0
? gridObject.transcendence_step
: 0
} }
special={'special' in object ? object.special : false} special={'special' in object ? object.special : false}
/> />

View file

@ -75,7 +75,7 @@ const Layout = ({ children }: PropsWithChildren<Props>) => {
return ( return (
<> <>
{appState.version ? ServerAvailable() : ''} {appState.version && ServerAvailable()}
<main>{children}</main> <main>{children}</main>
</> </>
) )

View file

@ -18,7 +18,7 @@ export type MentionRef = {
} }
export type MentionSuggestion = { export type MentionSuggestion = {
granblue_id: string granblueId: string
name: { name: {
[key: string]: string [key: string]: string
en: string en: string
@ -101,10 +101,10 @@ export const MentionList = forwardRef<MentionRef, Props>(
alt={item.name[locale]} alt={item.name[locale]}
src={ src={
item.type === 'character' item.type === 'character'
? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/${item.type}-square/${item.granblue_id}_01.jpg` ? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/${item.type}-square/${item.granblueId}_01.jpg`
: item.type === 'job' : item.type === 'job'
? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${item.granblue_id}.png` ? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${item.granblueId}.png`
: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/${item.type}-square/${item.granblue_id}.jpg` : `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/${item.type}-square/${item.granblueId}.jpg`
} }
/> />
</div> </div>

View file

@ -26,7 +26,7 @@ const AboutHead = ({ page }: Props) => {
name="description" name="description"
content={t(`page.descriptions.${currentPage}`)} content={t(`page.descriptions.${currentPage}`)}
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
{/* OpenGraph */} {/* OpenGraph */}

View file

@ -83,8 +83,8 @@ const ChangelogUnit = ({ id, type, image }: Props) => {
return ( return (
<div className={styles.unit} key={id}> <div className={styles.unit} key={id}>
<img alt={item ? item.name[locale] : ''} src={imageUrl()} /> <img alt={item && item.name[locale]} src={imageUrl()} />
<h4>{item ? item.name[locale] : ''}</h4> <h4>{item && item.name[locale]}</h4>
</div> </div>
) )
} }

View file

@ -9,6 +9,7 @@ import Button from '~components/common/Button'
import Overlay from '~components/common/Overlay' import Overlay from '~components/common/Overlay'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -46,21 +47,22 @@ const CharacterConflictModal = (props: Props) => {
else if (uncap > 2) suffix = '02' else if (uncap > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually) // Special casing for Lyria (and Young Cat eventually)
if (character?.granblue_id === '3030182000') { if (character?.granblueId === '3030182000') {
let element = 1 let element: GranblueElement | undefined
if ( if (
appState.grid.weapons.mainWeapon && appState.grid.weapons.mainWeapon &&
appState.grid.weapons.mainWeapon.element appState.grid.weapons.mainWeapon.element
) { ) {
element = appState.grid.weapons.mainWeapon.element element = appState.grid.weapons.mainWeapon.element
} else if (appState.party.element != 0) { } else {
element = appState.party.element element = ElementMap.wind
} }
suffix = `${suffix}_0${element}` suffix = `${suffix}_0${element?.id}`
} }
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${character?.granblue_id}_${suffix}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${character?.granblueId}_${suffix}.jpg`
} }
function openChange(open: boolean) { function openChange(open: boolean) {
@ -91,7 +93,7 @@ const CharacterConflictModal = (props: Props) => {
<li className={styles.character} key={`conflict-${i}`}> <li className={styles.character} key={`conflict-${i}`}>
<img <img
alt={character.object.name[locale]} alt={character.object.name[locale]}
src={imageUrl(character.object, character.uncap_level)} src={imageUrl(character.object, character.uncapLevel)}
/> />
<span>{character.object.name[locale]}</span> <span>{character.object.name[locale]}</span>
</li> </li>

View file

@ -16,14 +16,17 @@ import type { DetailsObject, 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 * as CharacterTransformer from '~transformers/CharacterTransformer'
import * as GridCharacterTransformer from '~transformers/GridCharacterTransformer'
import styles from './index.module.scss' import styles from './index.module.scss'
import { use } from 'i18next'
// Props // Props
interface Props { interface Props {
new: boolean new: boolean
editable: boolean editable: boolean
characters?: GridCharacter[] characters?: GridArray<GridCharacter>
createParty: (details?: DetailsObject) => Promise<Party> createParty: (details?: DetailsObject) => Promise<Party>
pushHistory?: (path: string) => void pushHistory?: (path: string) => void
} }
@ -46,7 +49,7 @@ const CharacterGrid = (props: Props) => {
const [errorAlertOpen, setErrorAlertOpen] = useState(false) const [errorAlertOpen, setErrorAlertOpen] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party } = useSnapshot(appState)
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
// Set up state for conflict management // Set up state for conflict management
@ -76,19 +79,39 @@ const CharacterGrid = (props: Props) => {
}>({}) }>({})
useEffect(() => { useEffect(() => {
setJob(appState.party.job) console.log('loading chara grid')
setJobSkills(appState.party.jobSkills) }, [])
setJobAccessory(appState.party.accessory)
}, [appState]) useEffect(() => {
setJob(appState.party.protagonist.job)
setJobSkills(
appState.party.protagonist.skills
? appState.party.protagonist.skills
: {
0: undefined,
1: undefined,
2: undefined,
3: undefined,
}
)
setJobAccessory(
appState.party.protagonist.accessory
? appState.party.protagonist.accessory
: undefined
)
}, [])
// 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 } = {}
Object.values(appState.grid.characters).map((o) => { const values = appState.party.grid.characters
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0 ? appState.party.grid.characters
: {}
Object.values(values).map((o) => {
o ? (initialPreviousUncapValues[o.position] = o.uncapLevel) : 0
}) })
setPreviousUncapValues(initialPreviousUncapValues) setPreviousUncapValues(initialPreviousUncapValues)
}, [appState.grid.characters]) }, [appState.party.grid.characters])
// Methods: Adding an object from search // Methods: Adding an object from search
function receiveCharacterFromSearch( function receiveCharacterFromSearch(
@ -121,8 +144,10 @@ const CharacterGrid = (props: Props) => {
async function handleCharacterResponse(data: any) { async function handleCharacterResponse(data: any) {
if (data.hasOwnProperty('conflicts')) { if (data.hasOwnProperty('conflicts')) {
setIncoming(data.incoming) setIncoming(CharacterTransformer.toObject(data.incoming))
setConflicts(data.conflicts) setConflicts(
data.conflicts.map((c: any) => GridCharacterTransformer.toObject(c))
)
setPosition(data.position) setPosition(data.position)
setModalOpen(true) setModalOpen(true)
} else { } else {
@ -140,13 +165,18 @@ const CharacterGrid = (props: Props) => {
party_id: partyId, party_id: partyId,
character_id: character.id, character_id: character.id,
position: position, position: position,
uncap_level: characterUncapLevel(character), uncapLevel: characterUncapLevel(character),
}, },
}) })
} }
function storeGridCharacter(gridCharacter: GridCharacter) { function storeGridCharacter(data: any) {
appState.grid.characters[gridCharacter.position] = gridCharacter const gridCharacter = GridCharacterTransformer.toObject(data)
appState.party.grid.characters = {
...appState.party.grid.characters,
[gridCharacter.position]: gridCharacter,
}
} }
async function resolveConflict() { async function resolveConflict() {
@ -164,7 +194,11 @@ const CharacterGrid = (props: Props) => {
// Remove conflicting characters from state // Remove conflicting characters from state
conflicts.forEach( conflicts.forEach(
(c) => (appState.grid.characters[c.position] = undefined) (c) =>
(appState.party.grid.characters = {
...appState.party.grid.characters,
[c.position]: null,
})
) )
// Reset conflict // Reset conflict
@ -186,7 +220,10 @@ const CharacterGrid = (props: Props) => {
async function removeCharacter(id: string) { async function removeCharacter(id: string) {
try { try {
const response = await api.endpoints.grid_characters.destroy({ id: id }) const response = await api.endpoints.grid_characters.destroy({ id: id })
appState.grid.characters[response.data.position] = undefined appState.party.grid.characters = {
...appState.party.grid.characters,
[response.data.position]: null,
}
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
@ -214,10 +251,10 @@ const CharacterGrid = (props: Props) => {
const team = response.data const team = response.data
setJob(team.job) setJob(team.job)
appState.party.job = team.job appState.party.protagonist.job = team.job
setJobSkills(team.job_skills) setJobSkills(team.job_skills)
appState.party.jobSkills = team.job_skills appState.party.protagonist.skills = team.job_skills
} }
} }
@ -244,7 +281,7 @@ const CharacterGrid = (props: Props) => {
// Update the current skills // Update the current skills
const newSkills = response.data.job_skills const newSkills = response.data.job_skills
setJobSkills(newSkills) setJobSkills(newSkills)
appState.party.jobSkills = newSkills appState.party.protagonist.skills = newSkills
}) })
.catch((error) => { .catch((error) => {
const data = error.response.data const data = error.response.data
@ -268,7 +305,7 @@ const CharacterGrid = (props: Props) => {
// Update the current skills // Update the current skills
const newSkills = response.data.job_skills const newSkills = response.data.job_skills
setJobSkills(newSkills) setJobSkills(newSkills)
appState.party.jobSkills = newSkills appState.party.protagonist.skills = newSkills
}) })
.catch((error) => { .catch((error) => {
const data = error.response.data const data = error.response.data
@ -291,7 +328,7 @@ const CharacterGrid = (props: Props) => {
) )
const team = response.data.party const team = response.data.party
setJobAccessory(team.accessory) setJobAccessory(team.accessory)
appState.party.accessory = team.accessory appState.party.protagonist.accessory = team.accessory
} }
} }
@ -378,10 +415,13 @@ const CharacterGrid = (props: Props) => {
position: number, position: number,
uncapLevel: number | undefined uncapLevel: number | undefined
) => { ) => {
const character = appState.grid.characters[position] const character = appState.party.grid.characters?.[position]
if (character && uncapLevel) { if (character && uncapLevel) {
character.uncap_level = uncapLevel character.uncapLevel = uncapLevel
appState.grid.characters[position] = character appState.party.grid.characters = {
...appState.party.grid.characters,
[position]: character,
}
} }
} }
@ -389,8 +429,8 @@ const CharacterGrid = (props: Props) => {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues } let newPreviousValues = { ...previousUncapValues }
if (grid.characters[position]) { if (party.grid.characters && party.grid.characters[position]) {
newPreviousValues[position] = grid.characters[position]?.uncap_level newPreviousValues[position] = party.grid.characters[position]?.uncapLevel
setPreviousUncapValues(newPreviousValues) setPreviousUncapValues(newPreviousValues)
} }
} }
@ -407,7 +447,7 @@ const CharacterGrid = (props: Props) => {
const payload = { const payload = {
character: { character: {
uncap_level: stage > 0 ? 6 : 5, uncapLevel: stage > 0 ? 6 : 5,
transcendence_step: stage, transcendence_step: stage,
}, },
} }
@ -474,10 +514,13 @@ const CharacterGrid = (props: Props) => {
position: number, position: number,
stage: number | undefined stage: number | undefined
) => { ) => {
const character = appState.grid.characters[position] const character = appState.party.grid.characters?.[position]
if (character && stage !== undefined) { if (character && stage !== undefined) {
character.transcendence_step = stage character.transcendenceStep = stage
appState.grid.characters[position] = character appState.party.grid.characters = {
...appState.party.grid.characters,
[position]: character,
}
} }
} }
@ -485,8 +528,8 @@ const CharacterGrid = (props: Props) => {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues } let newPreviousValues = { ...previousUncapValues }
if (grid.characters[position]) { if (party.grid.characters && party.grid.characters[position]) {
newPreviousValues[position] = grid.characters[position]?.uncap_level newPreviousValues[position] = party.grid.characters[position]?.uncapLevel
setPreviousTranscendenceStages(newPreviousValues) setPreviousTranscendenceStages(newPreviousValues)
} }
} }
@ -540,7 +583,9 @@ const CharacterGrid = (props: Props) => {
return ( return (
<li key={`grid_unit_${i}`}> <li key={`grid_unit_${i}`}>
<CharacterUnit <CharacterUnit
gridCharacter={grid.characters[i]} gridCharacter={
party.grid.characters ? party.grid.characters[i] : null
}
editable={props.editable} editable={props.editable}
position={i} position={i}
updateObject={receiveCharacterFromSearch} updateObject={receiveCharacterFromSearch}

View file

@ -2,14 +2,13 @@ import React from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import Button from '~components/common/Button'
import { import {
Hovercard, Hovercard,
HovercardContent, HovercardContent,
HovercardTrigger, HovercardTrigger,
} from '~components/common/Hovercard' } from '~components/common/Hovercard'
import Button from '~components/common/Button' import HovercardHeader from '~components/HovercardHeader'
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
import UncapIndicator from '~components/uncap/UncapIndicator'
import { import {
overMastery, overMastery,
@ -19,7 +18,6 @@ import {
import { ExtendedMastery } from '~types' import { ExtendedMastery } from '~types'
import styles from './index.module.scss' import styles from './index.module.scss'
import HovercardHeader from '~components/HovercardHeader'
interface Props { interface Props {
gridCharacter: GridCharacter gridCharacter: GridCharacter
@ -33,8 +31,7 @@ const CharacterHovercard = (props: Props) => {
const locale = const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light'] const tintElement = props.gridCharacter.object.element.slug
const tintElement = Element[props.gridCharacter.object.element]
function goTo() { function goTo() {
const urlSafeName = props.gridCharacter.object.name.en.replaceAll(' ', '_') const urlSafeName = props.gridCharacter.object.name.en.replaceAll(' ', '_')
@ -65,7 +62,7 @@ const CharacterHovercard = (props: Props) => {
} }
const overMasterySection = () => { const overMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.over_mastery) { if (props.gridCharacter && props.gridCharacter.mastery.overMastery) {
return ( return (
<section className={styles.mastery}> <section className={styles.mastery}>
<h5 className={tintElement}> <h5 className={tintElement}>
@ -75,7 +72,7 @@ const CharacterHovercard = (props: Props) => {
{[...Array(4)].map((e, i) => { {[...Array(4)].map((e, i) => {
const ringIndex = i + 1 const ringIndex = i + 1
const ringStat: ExtendedMastery = const ringStat: ExtendedMastery =
props.gridCharacter.over_mastery[i] props.gridCharacter.mastery.overMastery[i]
if (ringStat && ringStat.modifier && ringStat.modifier > 0) { if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
if (ringIndex === 1 || ringIndex === 2) { if (ringIndex === 1 || ringIndex === 2) {
return masteryElement(overMastery.a, ringStat) return masteryElement(overMastery.a, ringStat)
@ -95,8 +92,8 @@ const CharacterHovercard = (props: Props) => {
const aetherialMasterySection = () => { const aetherialMasterySection = () => {
if ( if (
props.gridCharacter && props.gridCharacter &&
props.gridCharacter.aetherial_mastery && props.gridCharacter.mastery.aetherialMastery &&
props.gridCharacter.aetherial_mastery.modifier > 0 props.gridCharacter.mastery.aetherialMastery.modifier > 0
) { ) {
return ( return (
<section className={styles.mastery}> <section className={styles.mastery}>
@ -106,7 +103,7 @@ const CharacterHovercard = (props: Props) => {
<ul> <ul>
{masteryElement( {masteryElement(
aetherialMastery, aetherialMastery,
props.gridCharacter.aetherial_mastery props.gridCharacter.mastery.aetherialMastery
)} )}
</ul> </ul>
</section> </section>
@ -115,7 +112,7 @@ const CharacterHovercard = (props: Props) => {
} }
const permanentMasterySection = () => { const permanentMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.perpetuity) { if (props.gridCharacter && props.gridCharacter.mastery.perpetuity) {
return ( return (
<section className={styles.mastery}> <section className={styles.mastery}>
<h5 className={tintElement}> <h5 className={tintElement}>
@ -135,7 +132,7 @@ const CharacterHovercard = (props: Props) => {
} }
const awakeningSection = () => { const awakeningSection = () => {
const gridAwakening = props.gridCharacter.awakening const gridAwakening = props.gridCharacter.mastery.awakening
if (gridAwakening) { if (gridAwakening) {
return ( return (

View file

@ -22,6 +22,13 @@ const emptyExtendedMastery: ExtendedMastery = {
strength: 0, strength: 0,
} }
const emptyRingset: CharacterOverMastery = {
1: { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery,
4: emptyExtendedMastery,
}
const MAX_AWAKENING_LEVEL = 9 const MAX_AWAKENING_LEVEL = 9
// Styles and icons // Styles and icons
@ -85,16 +92,29 @@ const CharacterModal = ({
}, [modalOpen]) }, [modalOpen])
useEffect(() => { useEffect(() => {
if (gridCharacter.aetherial_mastery) { console.log('Setting up grid character')
console.log(gridCharacter)
if (gridCharacter.mastery.overMastery) {
setRings(gridCharacter.mastery.overMastery)
} else {
setRings(emptyRingset)
}
if (gridCharacter.mastery.aetherialMastery) {
setEarring({ setEarring({
modifier: gridCharacter.aetherial_mastery.modifier, modifier: gridCharacter.mastery.aetherialMastery.modifier,
strength: gridCharacter.aetherial_mastery.strength, strength: gridCharacter.mastery.aetherialMastery.strength,
}) })
} }
setAwakening(gridCharacter.awakening.type) setAwakening(gridCharacter.mastery.awakening.type)
setAwakeningLevel(gridCharacter.awakening.level) setAwakeningLevel(
setPerpetuity(gridCharacter.perpetuity) gridCharacter.mastery.awakening.level
? gridCharacter.mastery.awakening.level
: 1
)
setPerpetuity(gridCharacter.mastery.perpetuity)
}, [gridCharacter]) }, [gridCharacter])
// Prepare the GridWeaponObject to send to the server // Prepare the GridWeaponObject to send to the server
@ -144,25 +164,18 @@ const CharacterModal = ({
rings || rings ||
aetherialMastery || aetherialMastery ||
awakening || awakening ||
gridCharacter.perpetuity !== perpetuity gridCharacter.mastery.perpetuity !== perpetuity
) )
} }
function ringsChanged() { function ringsChanged() {
// Create an empty ExtendedMastery object
const emptyRingset: CharacterOverMastery = {
1: { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery,
4: emptyExtendedMastery,
}
// Check if the current ringset is empty on the current GridCharacter and our local state // Check if the current ringset is empty on the current GridCharacter and our local state
const isEmptyRingset = const isEmptyRingset =
gridCharacter.over_mastery === undefined && isEqual(emptyRingset, rings) gridCharacter.mastery.overMastery === undefined &&
isEqual(emptyRingset, rings)
// Check if the ringset in local state is different from the one on the current GridCharacter // Check if the ringset in local state is different from the one on the current GridCharacter
const ringsChanged = !isEqual(gridCharacter.over_mastery, rings) const ringsChanged = !isEqual(gridCharacter.mastery.overMastery, rings)
// Return true if the ringset has been modified and is not empty // Return true if the ringset has been modified and is not empty
return ringsChanged && !isEmptyRingset return ringsChanged && !isEmptyRingset
@ -177,12 +190,12 @@ const CharacterModal = ({
// Check if the current earring is empty on the current GridCharacter and our local state // Check if the current earring is empty on the current GridCharacter and our local state
const isEmptyRingset = const isEmptyRingset =
gridCharacter.aetherial_mastery === undefined && gridCharacter.mastery.aetherialMastery === undefined &&
isEqual(emptyAetherialMastery, earring) isEqual(emptyAetherialMastery, earring)
// Check if the earring in local state is different from the one on the current GridCharacter // Check if the earring in local state is different from the one on the current GridCharacter
const aetherialMasteryChanged = !isEqual( const aetherialMasteryChanged = !isEqual(
gridCharacter.aetherial_mastery, gridCharacter.mastery.aetherialMastery,
earring earring
) )
@ -193,8 +206,8 @@ const CharacterModal = ({
function awakeningChanged() { function awakeningChanged() {
// Check if the awakening in local state is different from the one on the current GridCharacter // Check if the awakening in local state is different from the one on the current GridCharacter
const awakeningChanged = const awakeningChanged =
!isEqual(gridCharacter.awakening.type, awakening) || !isEqual(gridCharacter.mastery.awakening.type, awakening) ||
gridCharacter.awakening.level !== awakeningLevel gridCharacter.mastery.awakening.level !== awakeningLevel
// Return true if the awakening has been modified and is not empty // Return true if the awakening has been modified and is not empty
return awakeningChanged return awakeningChanged
@ -248,17 +261,21 @@ const CharacterModal = ({
function close() { function close() {
setEarring({ setEarring({
modifier: gridCharacter.aetherial_mastery modifier: gridCharacter.mastery.aetherialMastery
? gridCharacter.aetherial_mastery.modifier ? gridCharacter.mastery.aetherialMastery.modifier
: 0, : 0,
strength: gridCharacter.aetherial_mastery strength: gridCharacter.mastery.aetherialMastery
? gridCharacter.aetherial_mastery.strength ? gridCharacter.mastery.aetherialMastery.strength
: 0, : 0,
}) })
setRings(gridCharacter.over_mastery || emptyExtendedMastery) setRings(gridCharacter.mastery.overMastery || emptyExtendedMastery)
setAwakening(gridCharacter.awakening.type) setAwakening(gridCharacter.mastery.awakening.type)
setAwakeningLevel(gridCharacter.awakening.level) setAwakeningLevel(
gridCharacter.mastery.awakening.level
? gridCharacter.mastery.awakening.level
: 1
)
setAlertOpen(false) setAlertOpen(false)
setOpen(false) setOpen(false)
@ -305,13 +322,13 @@ const CharacterModal = ({
object="earring" object="earring"
dataSet={elementalizeAetherialMastery(gridCharacter)} dataSet={elementalizeAetherialMastery(gridCharacter)}
selectValue={ selectValue={
gridCharacter.aetherial_mastery gridCharacter.mastery.aetherialMastery
? gridCharacter.aetherial_mastery.modifier ? gridCharacter.mastery.aetherialMastery.modifier
: 0 : 0
} }
inputValue={ inputValue={
gridCharacter.aetherial_mastery gridCharacter.mastery.aetherialMastery
? gridCharacter.aetherial_mastery.strength ? gridCharacter.mastery.aetherialMastery.strength
: 0 : 0
} }
sendValidity={receiveValidity} sendValidity={receiveValidity}
@ -325,8 +342,8 @@ const CharacterModal = ({
<h3>{t('modals.characters.subtitles.awakening')}</h3> <h3>{t('modals.characters.subtitles.awakening')}</h3>
<AwakeningSelectWithInput <AwakeningSelectWithInput
dataSet={gridCharacter.object.awakenings} dataSet={gridCharacter.object.awakenings}
awakening={gridCharacter.awakening.type} awakening={gridCharacter.mastery.awakening.type}
level={gridCharacter.awakening.level} level={gridCharacter.mastery.awakening.level}
defaultAwakening={ defaultAwakening={
gridCharacter.object.awakenings.find( gridCharacter.object.awakenings.find(
(a) => a.slug === 'character-balanced' (a) => a.slug === 'character-balanced'
@ -364,7 +381,7 @@ const CharacterModal = ({
title={gridCharacter.object.name[locale]} title={gridCharacter.object.name[locale]}
subtitle={t('modals.characters.title')} subtitle={t('modals.characters.title')}
image={{ image={{
src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${gridCharacter.object.granblue_id}_01.jpg`, src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${gridCharacter.object.granblueId}_01.jpg`,
alt: gridCharacter.object.name[locale], alt: gridCharacter.object.name[locale],
}} }}
/> />

View file

@ -21,10 +21,10 @@ const CharacterResult = (props: Props) => {
const character = props.data const character = props.data
const characterUrl = () => { const characterUrl = () => {
let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_01.jpg` let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblueId}_01.jpg`
if (character.granblue_id === '3030182000') { if (character.granblueId === '3030182000') {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_01_01.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblueId}_01_01.jpg`
} }
return url return url
@ -40,9 +40,10 @@ const CharacterResult = (props: Props) => {
flb={character.uncap.flb} flb={character.uncap.flb}
ulb={character.uncap.ulb} ulb={character.uncap.ulb}
special={character.special} special={character.special}
transcendenceStage={character.uncap.ulb ? 5 : 0}
/> />
<div className={styles.tags}> <div className={styles.tags}>
<WeaponLabelIcon labelType={Element[character.element]} /> <WeaponLabelIcon labelType={character.element.slug} />
</div> </div>
</div> </div>
</li> </li>

View file

@ -21,6 +21,8 @@ import UncapIndicator from '~components/uncap/UncapIndicator'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import { ElementMap } from '~utils/elements'
import * as GridCharacterTransformer from '~transformers/GridCharacterTransformer'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
import SettingsIcon from '~public/icons/Settings.svg' import SettingsIcon from '~public/icons/Settings.svg'
@ -36,7 +38,7 @@ import type {
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props { interface Props {
gridCharacter?: GridCharacter gridCharacter: GridCharacter | null
position: number position: number
editable: boolean editable: boolean
removeCharacter: (id: string) => void removeCharacter: (id: string) => void
@ -109,7 +111,7 @@ const CharacterUnit = ({
function handlePerpetuityClick() { function handlePerpetuityClick() {
if (gridCharacter) { if (gridCharacter) {
let object: PerpetuityObject = { let object: PerpetuityObject = {
character: { perpetuity: !gridCharacter.perpetuity }, character: { perpetuity: !gridCharacter.mastery.perpetuity },
} }
updateCharacter(object) updateCharacter(object)
@ -144,21 +146,23 @@ const CharacterUnit = ({
// Save the server's response to state // Save the server's response to state
function processResult(response: AxiosResponse) { function processResult(response: AxiosResponse) {
const gridCharacter: GridCharacter = response.data const gridCharacter: GridCharacter = GridCharacterTransformer.toObject(
response.data
)
let character = cloneDeep(gridCharacter) let character = cloneDeep(gridCharacter)
if (character.over_mastery) { if (character.mastery.overMastery) {
const overMastery: CharacterOverMastery = { const overMastery: CharacterOverMastery = {
1: gridCharacter.over_mastery[0], 1: gridCharacter.mastery.overMastery[0],
2: gridCharacter.over_mastery[1], 2: gridCharacter.mastery.overMastery[1],
3: gridCharacter.over_mastery[2], 3: gridCharacter.mastery.overMastery[2],
4: gridCharacter.over_mastery[3], 4: gridCharacter.mastery.overMastery[3],
} }
character.over_mastery = overMastery character.mastery.overMastery = overMastery
} }
appState.grid.characters[gridCharacter.position] = character appState.party.grid.characters[gridCharacter.position] = character
} }
function processError(error: any) { function processError(error: any) {
@ -187,23 +191,28 @@ const CharacterUnit = ({
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = '01' let suffix = '01'
if (gridCharacter.transcendence_step > 0) suffix = '04' if (
else if (gridCharacter.uncap_level >= 5) suffix = '03' gridCharacter.transcendenceStep &&
else if (gridCharacter.uncap_level > 2) suffix = '02' gridCharacter.transcendenceStep > 0
)
suffix = '04'
else if (gridCharacter.uncapLevel >= 5) suffix = '03'
else if (gridCharacter.uncapLevel > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually) // Special casing for Lyria (and Young Cat eventually)
if (gridCharacter.object.granblue_id === '3030182000') { if (gridCharacter.object.granblueId === '3030182000') {
let element = 1 let element: GranblueElement | undefined
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) { if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
element = grid.weapons.mainWeapon.element element = grid.weapons.mainWeapon.element
} else if (party.element != 0) { } else {
element = party.element element = ElementMap.wind
} }
suffix = `${suffix}_0${element}` suffix = `${suffix}_0${element}`
} }
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblueId}_${suffix}.jpg`
} }
setImageUrl(imgSrc) setImageUrl(imgSrc)
@ -292,7 +301,7 @@ const CharacterUnit = ({
if (gridCharacter) { if (gridCharacter) {
const classes = classNames({ const classes = classNames({
[styles.perpetuity]: true, [styles.perpetuity]: true,
[styles.empty]: !gridCharacter.perpetuity, [styles.empty]: !gridCharacter.mastery.perpetuity,
}) })
return <i className={classes} onClick={handlePerpetuityClick} /> return <i className={classes} onClick={handlePerpetuityClick} />
@ -348,8 +357,8 @@ const CharacterUnit = ({
type="character" type="character"
flb={character.uncap.flb || false} flb={character.uncap.flb || false}
ulb={character.uncap.ulb || false} ulb={character.uncap.ulb || false}
uncapLevel={gridCharacter.uncap_level} uncapLevel={gridCharacter.uncapLevel}
transcendenceStage={gridCharacter.transcendence_step} transcendenceStage={gridCharacter.transcendenceStep}
position={gridCharacter.position} position={gridCharacter.position}
editable={editable} editable={editable}
updateUncap={passUncapData} updateUncap={passUncapData}

View file

@ -13,6 +13,14 @@
color: inherit; color: inherit;
z-index: 10; z-index: 10;
@include breakpoint(phone) {
place-items: flex-end;
overflow-y: hidden;
&.filter {
}
}
.dialogContent { .dialogContent {
$multiplier: 4; $multiplier: 4;
@ -51,11 +59,11 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
min-width: inherit; min-width: inherit;
min-height: 90vh; min-height: inherit;
transform: initial; transform: initial;
left: 0; left: 0;
right: 0; right: 0;
top: 5vh; top: $unit-10x;
height: auto; height: auto;
width: 100%; width: 100%;
} }
@ -101,110 +109,6 @@
overflow-y: auto; overflow-y: auto;
} }
} }
&.Conflict {
$weapon-diameter: 14rem;
.Content {
display: flex;
flex-direction: column;
gap: $unit-4x;
padding: $unit-4x $unit-4x $unit-2x $unit-4x;
& > p {
font-size: $font-regular;
line-height: 1.4;
strong {
font-weight: $bold;
}
&:lang(ja) {
line-height: 1.4;
}
}
}
.weapon,
.character {
display: flex;
flex-direction: column;
gap: $unit;
text-align: center;
width: $weapon-diameter;
font-weight: $medium;
img {
border-radius: 1rem;
width: $weapon-diameter;
height: auto;
}
span {
line-height: 1.3;
}
}
.Diagram {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: flex-start;
&.CharacterDiagram {
align-items: center;
}
ul {
align-items: center;
display: flex;
flex-direction: column;
gap: $unit-2x;
}
.wrapper {
display: flex;
justify-content: center;
width: 100%;
}
.arrow {
align-items: center;
color: $grey-55;
display: flex;
font-size: 4rem;
text-align: center;
height: $weapon-diameter;
justify-content: center;
}
}
footer {
display: flex;
flex-direction: row;
gap: $unit;
.Button {
font-size: $font-regular;
padding: ($unit * 1.5) ($unit * 2);
width: 100%;
&.btn-disabled {
background: $grey-90;
color: $grey-70;
cursor: not-allowed;
}
&:not(.btn-disabled) {
background: $grey-90;
color: $grey-50;
&:hover {
background: $grey-80;
}
}
}
}
}
} }
@keyframes openModalDesktop { @keyframes openModalDesktop {
@ -221,11 +125,20 @@
@keyframes slideUp { @keyframes slideUp {
0% { 0% {
transform: translate(0%, 100%); transform: translateY(400px);
animation-timing-function: ease-out;
}
60% {
transform: translateY(-30px);
animation-timing-function: ease-in;
}
80% {
transform: translateY(10px);
animation-timing-function: ease-out;
} }
100% { 100% {
transform: translate(0, 0%); transform: translateY(0px);
animation-timing-function: ease-in;
} }
} }
} }

View file

@ -11,6 +11,7 @@ interface Props
React.DialogHTMLAttributes<HTMLDivElement>, React.DialogHTMLAttributes<HTMLDivElement>,
HTMLDivElement HTMLDivElement
> { > {
wrapperClassName?: string
headerref?: React.RefObject<HTMLDivElement> headerref?: React.RefObject<HTMLDivElement>
footerref?: React.RefObject<HTMLDivElement> footerref?: React.RefObject<HTMLDivElement>
scrollable?: boolean scrollable?: boolean
@ -127,7 +128,16 @@ const DialogContent = React.forwardRef<HTMLDivElement, Props>(function Dialog(
return ( return (
<DialogPrimitive.Portal> <DialogPrimitive.Portal>
<dialog className={styles.dialog}> <dialog
className={classNames(
{
[styles.dialog]: true,
},
props.wrapperClassName
?.split(' ')
.map((className) => styles[className])
)}
>
<DialogPrimitive.Content <DialogPrimitive.Content
{...props} {...props}
className={classes} className={classes}

View file

@ -16,4 +16,11 @@
display: flex; display: flex;
gap: $unit; gap: $unit;
} }
@include breakpoint(phone) {
position: fixed;
bottom: $unit-14x;
left: 0;
right: 0;
}
} }

View file

@ -36,6 +36,14 @@
outline: none; outline: none;
} }
p.empty:first-child::before {
color: var(--text-tertiary);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
&.bound { &.bound {
background-color: var(--input-bound-bg); background-color: var(--input-bound-bg);
@ -74,21 +82,21 @@
h1 { h1 {
font-size: $font-xlarge; font-size: $font-xlarge;
font-weight: $medium; font-weight: $medium;
margin: $unit 0; margin: $unit-2x 0;
text-align: left; text-align: left;
} }
h2 { h2 {
font-size: $font-large; font-size: $font-large;
font-weight: $medium; font-weight: $medium;
margin: $unit 0; margin: $unit-2x 0;
text-align: left; text-align: left;
} }
h3 { h3 {
font-size: $font-regular; font-size: $font-regular;
font-weight: $medium; font-weight: $medium;
margin: $unit 0; margin: $unit-2x 0;
text-align: left; text-align: left;
} }
@ -96,6 +104,7 @@
border-radius: $item-corner-small; border-radius: $item-corner-small;
background: var(--highlight-bg); background: var(--highlight-bg);
color: var(--highlight-text); color: var(--highlight-text);
font-weight: $normal;
padding: 1px $unit-fourth; padding: 1px $unit-fourth;
} }
@ -116,15 +125,18 @@
.mention { .mention {
border-radius: $item-corner-small; border-radius: $item-corner-small;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px rgba(0, 0, 0, 0.25); 0 1px 0px var(--null-shadow);
background: var(--card-bg); background: var(--null-bg);
color: var(--text-primary); color: var(--text-primary);
font-weight: $medium; font-weight: $medium;
font-size: 15px; font-size: 15px;
padding: 1px $unit-half; padding: 1px $unit-half;
transition: all 0.1s ease-out;
&:hover { &:hover {
background: var(--card-bg-hover); background: var(--null-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--null-shadow-hover);
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
} }
@ -136,6 +148,7 @@
color: var(--fire-text); color: var(--fire-text);
&:hover { &:hover {
background: var(--fire-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--fire-shadow-hover); 0 1px 0px var(--fire-shadow-hover);
color: var(--fire-text-hover); color: var(--fire-text-hover);
@ -149,6 +162,7 @@
color: var(--water-text); color: var(--water-text);
&:hover { &:hover {
background: var(--water-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--water-shadow-hover); 0 1px 0px var(--water-shadow-hover);
color: var(--water-text-hover); color: var(--water-text-hover);
@ -162,6 +176,7 @@
color: var(--earth-text); color: var(--earth-text);
&:hover { &:hover {
background: var(--earth-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--earth-shadow-hover); 0 1px 0px var(--earth-shadow-hover);
color: var(--earth-text-hover); color: var(--earth-text-hover);
@ -188,6 +203,7 @@
color: var(--dark-text); color: var(--dark-text);
&:hover { &:hover {
background: var(--dark-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--dark-shadow-hover); 0 1px 0px var(--dark-shadow-hover);
color: var(--dark-text-hover); color: var(--dark-text-hover);
@ -201,6 +217,7 @@
color: var(--light-text); color: var(--light-text);
&:hover { &:hover {
background: var(--light-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--light-shadow-hover); 0 1px 0px var(--light-shadow-hover);
color: var(--light-text-hover); color: var(--light-text-hover);

View file

@ -1,9 +1,12 @@
import { ComponentProps, useCallback, useEffect } from 'react' import { ComponentProps, useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
import { useEditor, EditorContent } from '@tiptap/react' import { useEditor, EditorContent } from '@tiptap/react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link' import Link from '@tiptap/extension-link'
import Highlight from '@tiptap/extension-highlight' import Highlight from '@tiptap/extension-highlight'
import Placeholder from '@tiptap/extension-placeholder'
import Typography from '@tiptap/extension-typography' import Typography from '@tiptap/extension-typography'
import Youtube from '@tiptap/extension-youtube' import Youtube from '@tiptap/extension-youtube'
import CustomMention from '~extensions/CustomMention' import CustomMention from '~extensions/CustomMention'
@ -18,7 +21,7 @@ import ItalicIcon from 'remixicon-react/ItalicIcon'
import StrikethroughIcon from 'remixicon-react/StrikethroughIcon' import StrikethroughIcon from 'remixicon-react/StrikethroughIcon'
import UnorderedListIcon from 'remixicon-react/ListUnorderedIcon' import UnorderedListIcon from 'remixicon-react/ListUnorderedIcon'
import OrderedListIcon from '~public/icons/remix/list-ordered-2.svg' import OrderedListIcon from '~public/icons/remix/list-ordered-2.svg'
import PaintbrushIcon from 'remixicon-react/PaintbrushLineIcon' import PaintbrushIcon from 'remixicon-react/PaintBrushLineIcon'
import H1Icon from 'remixicon-react/H1Icon' import H1Icon from 'remixicon-react/H1Icon'
import H2Icon from 'remixicon-react/H2Icon' import H2Icon from 'remixicon-react/H2Icon'
import H3Icon from 'remixicon-react/H3Icon' import H3Icon from 'remixicon-react/H3Icon'
@ -45,6 +48,8 @@ const Editor = ({
const router = useRouter() const router = useRouter()
const locale = router.locale || 'en' const locale = router.locale || 'en'
const { t } = useTranslation('common')
useEffect(() => { useEffect(() => {
editor?.commands.setContent(formatContent(content)) editor?.commands.setContent(formatContent(content))
}, [content]) }, [content])
@ -72,6 +77,10 @@ const Editor = ({
}), }),
Link, Link,
Highlight, Highlight,
Placeholder.configure({
emptyEditorClass: styles.empty,
placeholder: t('modals.edit_team.placeholders.description'),
}),
Typography, Typography,
CustomMention.configure({ CustomMention.configure({
renderLabel({ options, node }) { renderLabel({ options, node }) {

View file

@ -21,12 +21,6 @@
&.flush { &.flush {
padding: 0; padding: 0;
} }
.Arrow {
fill: var(--dialog-bg);
filter: drop-shadow(0px 1px 1px rgb(0 0 0 / 0.18));
margin-top: -1px;
}
} }
.trigger { .trigger {

View file

@ -0,0 +1,5 @@
.arrow {
fill: var(--dialog-bg);
filter: drop-shadow(0px 1px 1px rgb(0 0 0 / 0.18));
margin-top: -1px;
}

View file

@ -63,6 +63,10 @@
&.table { &.table {
min-width: $unit * 30; min-width: $unit * 30;
@include breakpoint(phone) {
width: 100%;
}
} }
&.hidden { &.hidden {

View file

@ -14,6 +14,10 @@
border-radius: 9999px; border-radius: 9999px;
height: 3px; height: 3px;
} }
&.table {
flex-grow: 1;
}
} }
.range { .range {

View file

@ -54,6 +54,7 @@ const SliderTableField = (props: Props) => {
max={props.max} max={props.max}
step={props.step} step={props.step}
value={[props.value ? props.value : 0]} value={[props.value ? props.value : 0]}
className="table"
onValueChange={handleValueChange} onValueChange={handleValueChange}
onValueCommit={handleValueCommit} onValueCommit={handleValueCommit}
/> />

View file

@ -36,6 +36,14 @@
} }
} }
&.switch {
@include breakpoint(phone) {
align-items: center;
flex-direction: row;
justify-content: space-between;
}
}
.left { .left {
align-items: center; align-items: center;
display: flex; display: flex;

View file

@ -23,25 +23,26 @@ const TableField = (props: Props) => {
) )
const image = () => { const image = () => {
return props.image && props.image.src.length > 0 ? ( return (
<div props.image &&
className={classNames( props.image.src.length > 0 && (
{ <div
[styles.preview]: true, className={classNames(
}, {
props.image.className [styles.preview]: true,
?.split(' ') },
.map((className) => styles[className]) props.image.className
)} ?.split(' ')
> .map((className) => styles[className])
<img )}
alt={props.image.alt} >
srcSet={props.image.src.join(', ')} <img
src={props.image.src[0]} alt={props.image.alt}
/> srcSet={props.image.src.join(', ')}
</div> src={props.image.src[0]}
) : ( />
'' </div>
)
) )
} }

View file

@ -24,7 +24,13 @@ const ToolbarIcon = ({ editor, action, level, icon, onClick }: Props) => {
}) })
return ( return (
<Tooltip content={t(`toolbar.tooltips.${action}`)}> <Tooltip
content={
level
? t(`toolbar.tooltips.${action}`, { level: level })
: t(`toolbar.tooltips.${action}`)
}
>
<button onClick={onClick} className={classes}> <button onClick={onClick} className={classes}>
{icon} {icon}
</button> </button>

View file

@ -19,7 +19,7 @@ const GuidebookResult = (props: Props) => {
<li className={styles.result} onClick={props.onClick}> <li className={styles.result} onClick={props.onClick}>
<img <img
alt={guidebook.name[locale]} alt={guidebook.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/guidebooks/book_${guidebook.granblue_id}.png`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/guidebooks/book_${guidebook.granblueId}.png`}
/> />
<div className={styles.info}> <div className={styles.info}>
<h5>{guidebook.name[locale]}</h5> <h5>{guidebook.name[locale]}</h5>

View file

@ -99,7 +99,7 @@ const GuidebookUnit = ({
// Methods: Image string generation // Methods: Image string generation
function generateImageUrl() { function generateImageUrl() {
let imgSrc = guidebook let imgSrc = guidebook
? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/guidebooks/book_${guidebook.granblue_id}.png` ? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/guidebooks/book_${guidebook.granblueId}.png`
: '' : ''
setImageUrl(imgSrc) setImageUrl(imgSrc)

View file

@ -39,5 +39,6 @@
@include breakpoint(phone) { @include breakpoint(phone) {
gap: $unit-4x; gap: $unit-4x;
margin-bottom: $unit * 24;
} }
} }

View file

@ -402,7 +402,8 @@ const FilterModal = (props: Props) => {
<Dialog open={open} onOpenChange={openChange}> <Dialog open={open} onOpenChange={openChange}>
<DialogTrigger asChild>{props.children}</DialogTrigger> <DialogTrigger asChild>{props.children}</DialogTrigger>
<DialogContent <DialogContent
className="Filter" className="filter"
wrapperClassName="filter"
headerref={headerRef} headerref={headerRef}
footerref={footerRef} footerref={footerRef}
onEscapeKeyDown={onEscapeKeyDown} onEscapeKeyDown={onEscapeKeyDown}

View file

@ -11,7 +11,7 @@ const NewHead = () => {
{/* HTML */} {/* HTML */}
<title>{t('page.titles.new')}</title> <title>{t('page.titles.new')}</title>
<meta name="description" content={t('page.descriptions.new')} /> <meta name="description" content={t('page.descriptions.new')} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
{/* OpenGraph */} {/* OpenGraph */}

View file

@ -20,7 +20,7 @@ const ProfileHead = ({ user }: Props) => {
username: user.username, username: user.username,
})} })}
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
{/* OpenGraph */} {/* OpenGraph */}

View file

@ -9,7 +9,7 @@ const SavedHead = () => {
return ( return (
<Head> <Head>
<title>{t('page.titles.saved')}</title> <title>{t('page.titles.saved')}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
<meta property="og:title" content={t('page.titles.saved')} /> <meta property="og:title" content={t('page.titles.saved')} />

View file

@ -11,7 +11,6 @@ const TeamsHead = () => {
{/* HTML */} {/* HTML */}
<title>{t('page.titles.discover')}</title> <title>{t('page.titles.discover')}</title>
<meta name="description" content={t('page.descriptions.discover')} /> <meta name="description" content={t('page.descriptions.discover')} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
{/* OpenGraph */} {/* OpenGraph */}

View file

@ -1,4 +1,4 @@
.JobAccessoryItem { .item {
background: none; background: none;
border-radius: $input-corner; border-radius: $input-corner;
border: none; border: none;

View file

@ -18,13 +18,13 @@ const JobAccessoryItem = ({ accessory, selected }: Props) => {
return ( return (
<RadioGroup.Item <RadioGroup.Item
className="JobAccessoryItem" className={styles.item}
data-state={selected ? 'checked' : 'unchecked'} data-state={selected ? 'checked' : 'unchecked'}
value={accessory.id} value={accessory.id}
> >
<img <img
alt={accessory.name[locale]} alt={accessory.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/accessory-grid/${accessory.granblue_id}.jpg`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/accessory-grid/${accessory.granblueId}.jpg`}
/> />
<h4>{accessory.name[locale]}</h4> <h4>{accessory.name[locale]}</h4>
</RadioGroup.Item> </RadioGroup.Item>

View file

@ -12,7 +12,7 @@
margin: 0 0 $unit $unit; margin: 0 0 $unit $unit;
} }
&.ReadOnly { &.readOnly {
min-width: inherit; min-width: inherit;
max-width: inherit; max-width: inherit;
} }
@ -27,7 +27,7 @@
max-width: initial; max-width: initial;
} }
.Accessories { .accessories {
display: grid; display: grid;
gap: $unit; gap: $unit;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
@ -38,7 +38,7 @@
} }
} }
.EquippedAccessory { .equipped {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit-2x; gap: $unit-2x;
@ -47,7 +47,7 @@
margin: 0; margin: 0;
} }
.Accessory { .accessory {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit; gap: $unit;

View file

@ -6,7 +6,6 @@ import classNames from 'classnames'
import capitalizeFirstLetter from '~utils/capitalizeFirstLetter' import capitalizeFirstLetter from '~utils/capitalizeFirstLetter'
import * as RadioGroup from '@radix-ui/react-radio-group' import * as RadioGroup from '@radix-ui/react-radio-group'
import Button from '~components/common/Button'
import { import {
Popover, Popover,
PopoverTrigger, PopoverTrigger,
@ -91,7 +90,7 @@ const JobAccessoryPopover = ({
)} )}
</h3> </h3>
<RadioGroup.Root <RadioGroup.Root
className="Accessories" className={styles.accessories}
onValueChange={handleAccessorySelected} onValueChange={handleAccessorySelected}
> >
{accessories.map((accessory) => ( {accessories.map((accessory) => (
@ -110,17 +109,17 @@ const JobAccessoryPopover = ({
) )
const readOnly = currentAccessory ? ( const readOnly = currentAccessory ? (
<div className="EquippedAccessory"> <div className={styles.equipped}>
<h3> <h3>
{t('equipped')}{' '} {t('equipped')}{' '}
{job.accessory_type === 1 {job.accessory_type === 1
? `${t('accessories.paladin')}s` ? `${t('accessories.paladin')}s`
: t('accessories.manadiver')} : t('accessories.manadiver')}
</h3> </h3>
<div className="Accessory"> <div className={styles.accessory}>
<img <img
alt={currentAccessory.name[locale]} alt={currentAccessory.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/accessory-grid/${currentAccessory.granblue_id}.jpg`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/accessory-grid/${currentAccessory.granblueId}.jpg`}
/> />
<h4>{currentAccessory.name[locale]}</h4> <h4>{currentAccessory.name[locale]}</h4>
</div> </div>

View file

@ -39,8 +39,8 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
// Set current job from state on mount // Set current job from state on mount
useEffect(() => { useEffect(() => {
if (party.job?.id !== '-1') { if (party.protagonist.job?.id !== '-1') {
setCurrentJob(party.job) setCurrentJob(party.protagonist.job)
} }
}, []) }, [])
@ -95,7 +95,7 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
icon={{ icon={{
alt: item.name[locale], alt: item.name[locale],
src: [ src: [
`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${item.granblue_id}.png`, `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${item.granblueId}.png`,
], ],
}} }}
> >
@ -119,7 +119,7 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
icon={{ icon={{
alt: currentJob ? currentJob.name[locale] : '', alt: currentJob ? currentJob.name[locale] : '',
src: currentJob src: currentJob
? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${currentJob.granblue_id}.png` ? `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${currentJob.granblueId}.png`
: '', : '',
}} }}
open={open} open={open}

View file

@ -85,27 +85,28 @@ const JobImage = ({
} }
const accessoryPopover = () => { const accessoryPopover = () => {
return job && accessories ? ( return (
<JobAccessoryPopover job &&
buttonref={buttonRef} accessories && (
currentAccessory={currentAccessory} <JobAccessoryPopover
accessories={accessories} buttonref={buttonRef}
editable={editable} currentAccessory={currentAccessory}
open={open} accessories={accessories}
job={job} editable={editable}
onAccessorySelected={onAccessorySelected} open={open}
onOpenChange={handlePopoverOpenChanged} job={job}
> onAccessorySelected={onAccessorySelected}
{accessoryButton()} onOpenChange={handlePopoverOpenChanged}
</JobAccessoryPopover> >
) : ( {accessoryButton()}
'' </JobAccessoryPopover>
)
) )
} }
return ( return (
<div className={styles.image}> <div className={styles.image}>
{hasAccessory ? accessoryPopover() : ''} {hasAccessory && accessoryPopover()}
{job && job.id !== '-1' ? image : ''} {job && job.id !== '-1' && image}
<div className={styles.overlay} /> <div className={styles.overlay} />
</div> </div>
) )

View file

@ -11,14 +11,14 @@ import SearchModal from '~components/search/SearchModal'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import type { JobSkillObject, SearchableObject } from '~types' import type { SearchableObject } from '~types'
import styles from './index.module.scss' import styles from './index.module.scss'
// Props // Props
interface Props { interface Props {
job?: Job job?: Job
jobSkills: JobSkillObject jobSkills: JobSkillList
jobAccessory?: JobAccessory jobAccessory?: JobAccessory
editable: boolean editable: boolean
saveJob: (job?: Job) => void saveJob: (job?: Job) => void
@ -76,8 +76,11 @@ const JobSection = (props: Props) => {
useEffect(() => { useEffect(() => {
if (job) { if (job) {
if ((party.job && job.id != party.job.id) || !party.job) if (
appState.party.job = job (party.protagonist.job && job.id != party.protagonist.job.id) ||
!party.protagonist.job
)
appState.party.protagonist.job = job
if (job.row === '1') setNumSkills(3) if (job.row === '1') setNumSkills(3)
else setNumSkills(4) else setNumSkills(4)
fetchJobAccessories() fetchJobAccessories()
@ -178,7 +181,7 @@ const JobSection = (props: Props) => {
<div className={styles.name}> <div className={styles.name}>
<img <img
alt={job?.name[locale]} alt={job?.name[locale]}
src={`/images/job-icons/${job?.granblue_id}.png`} src={`/images/job-icons/${job?.granblueId}.png`}
/> />
<h3>{job?.name[locale]}</h3> <h3>{job?.name[locale]}</h3>
</div> </div>
@ -188,7 +191,7 @@ const JobSection = (props: Props) => {
return ( return (
<section className={styles.job}> <section className={styles.job}>
<JobImage <JobImage
job={party.job} job={party.protagonist.job}
currentAccessory={currentAccessory} currentAccessory={currentAccessory}
accessories={accessories} accessories={accessories}
editable={props.editable} editable={props.editable}
@ -198,21 +201,23 @@ const JobSection = (props: Props) => {
<div className={styles.details}> <div className={styles.details}>
{props.editable ? ( {props.editable ? (
<JobDropdown <JobDropdown
currentJob={party.job?.id} currentJob={party.protagonist.job?.id}
onChange={receiveJob} onChange={receiveJob}
ref={selectRef} ref={selectRef}
/> />
) : ( ) : (
<div className={styles.name}> <div className={styles.name}>
{party.job ? ( {party.protagonist.job && (
<img <img
alt={party.job.name[locale]} alt={party.protagonist.job.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${party.job.granblue_id}.png`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-icons/${party.protagonist.job.granblueId}.png`}
/> />
) : (
''
)} )}
<h3>{party.job ? party.job.name[locale] : t('no_job')}</h3> <h3>
{party.protagonist.job
? party.protagonist.job.name[locale]
: t('no_job')}
</h3>
</div> </div>
)} )}

View file

@ -107,7 +107,7 @@ const JobSkillItem = React.forwardRef<HTMLDivElement, Props>(
} else { } else {
jsx = ( jsx = (
<div className={imageClasses}> <div className={imageClasses}>
{editable && hasJob ? <PlusIcon /> : ''} {editable && hasJob && <PlusIcon />}
</div> </div>
) )
} }

View file

@ -225,7 +225,7 @@ const AXSelect = (props: Props) => {
if (modifierSet == 0) { if (modifierSet == 0) {
axOptionElements = axOptions.map((ax, i) => { axOptionElements = axOptions.map((ax, i) => {
return ( return (
<SelectItem key={i} value={ax.id} data-granblue-id={ax.granblue_id}> <SelectItem key={i} value={ax.id} data-granblue-id={ax.granblueId}>
{ax.name[locale]} {ax.name[locale]}
</SelectItem> </SelectItem>
) )

View file

@ -19,11 +19,11 @@ const emptyRing: ExtendedMastery = {
} }
interface Props { interface Props {
gridCharacter: GridCharacter rings: CharacterOverMastery
sendValues: (overMastery: CharacterOverMastery) => void sendValues: (overMastery: CharacterOverMastery) => void
} }
const RingSelect = ({ gridCharacter, sendValues }: Props) => { const RingSelect = ({ rings: overMastery, sendValues }: Props) => {
// Ring value states // Ring value states
const [rings, setRings] = useState<CharacterOverMastery>({ const [rings, setRings] = useState<CharacterOverMastery>({
1: { ...emptyRing, modifier: 1 }, 1: { ...emptyRing, modifier: 1 },
@ -33,15 +33,13 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
}) })
useEffect(() => { useEffect(() => {
if (gridCharacter.over_mastery) { setRings({
setRings({ 1: overMastery[1],
1: gridCharacter.over_mastery[1], 2: overMastery[2],
2: gridCharacter.over_mastery[2], 3: overMastery[3],
3: gridCharacter.over_mastery[3], 4: overMastery[4],
4: gridCharacter.over_mastery[4], })
}) }, [overMastery])
}
}, [gridCharacter])
useEffect(() => { useEffect(() => {
sendValues(rings) sendValues(rings)
@ -54,7 +52,7 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
ja: 'EXリミットボーナスなし', ja: 'EXリミットボーナスなし',
}, },
id: 0, id: 0,
granblue_id: '', granblueId: '',
slug: 'no-bonus', slug: 'no-bonus',
minValue: 0, minValue: 0,
maxValue: 0, maxValue: 0,
@ -80,6 +78,7 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
function receiveRingValues(index: number, left: number, right: number) { function receiveRingValues(index: number, left: number, right: number) {
// console.log(`Receiving values from ${index}: ${left} ${right}`) // console.log(`Receiving values from ${index}: ${left} ${right}`)
if (index == 1 || index == 2) { if (index == 1 || index == 2) {
console.log('1 or 2')
setSyncedRingValues(index, right) setSyncedRingValues(index, right)
} else if (index == 3 && left == 0) { } else if (index == 3 && left == 0) {
setRings({ setRings({
@ -132,6 +131,7 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
{[...Array(4)].map((e, i) => { {[...Array(4)].map((e, i) => {
const index = i + 1 const index = i + 1
const ringStat = rings[index] const ringStat = rings[index]
console.log(ringStat)
return ( return (
<ExtendedMasterySelect <ExtendedMasterySelect
@ -140,8 +140,8 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
key={`ring-${index}`} key={`ring-${index}`}
dataSet={dataSet(index)} dataSet={dataSet(index)}
leftSelectDisabled={index === 1 || index === 2} leftSelectDisabled={index === 1 || index === 2}
leftSelectValue={ringStat.modifier ? ringStat.modifier : 0} leftSelectValue={ringStat?.modifier ? ringStat?.modifier : 0}
rightSelectValue={ringStat.strength ? ringStat.strength : 0} rightSelectValue={ringStat?.strength ? ringStat?.strength : 0}
sendValues={(left: number, right: number) => { sendValues={(left: number, right: number) => {
receiveRingValues(index, left, right) receiveRingValues(index, left, right)
}} }}

View file

@ -297,20 +297,21 @@ const EditPartyModal = ({
// Methods: Modification checking // Methods: Modification checking
function hasBeenModified() { function hasBeenModified() {
const nameChanged = const nameChanged =
name !== party.name && !(name === '' && party.name === undefined) name !== party.name &&
!(name === '' && (party.name === undefined || party.name === null))
const descriptionChanged = const descriptionChanged =
description !== party.description && description !== party.description &&
!(description === '' && party.description === undefined) !(description === '' && party.description === undefined)
const raidChanged = raid !== party.raid const raidChanged = raid !== party.raid
const chargeAttackChanged = chargeAttack !== party.chargeAttack const chargeAttackChanged = chargeAttack !== party.details.chargeAttack
const fullAutoChanged = fullAuto !== party.fullAuto const fullAutoChanged = fullAuto !== party.details.fullAuto
const autoGuardChanged = autoGuard !== party.autoGuard const autoGuardChanged = autoGuard !== party.details.autoGuard
const autoSummonChanged = autoSummon !== party.autoSummon const autoSummonChanged = autoSummon !== party.details.autoSummon
const clearTimeChanged = clearTime !== party.clearTime const clearTimeChanged = clearTime !== party.details.clearTime
const turnCountChanged = turnCount !== party.turnCount const turnCountChanged = turnCount !== party.details.turnCount
const buttonCountChanged = buttonCount !== party.buttonCount const buttonCountChanged = buttonCount !== party.details.buttonCount
const chainCountChanged = chainCount !== party.chainCount const chainCountChanged = chainCount !== party.details.chainCount
// Debugging for if you need to check if a value is being changed // Debugging for if you need to check if a value is being changed
// console.log(` // console.log(`
@ -348,14 +349,17 @@ const EditPartyModal = ({
setName(party.name ? party.name : '') setName(party.name ? party.name : '')
setDescription(party.description ? party.description : '') setDescription(party.description ? party.description : '')
setRaid(party.raid) setRaid(party.raid)
setAutoGuard(party.autoGuard) setAutoGuard(party.details.autoGuard)
setAutoSummon(party.autoSummon) setAutoSummon(party.details.autoSummon)
setFullAuto(party.fullAuto) setFullAuto(party.details.fullAuto)
setChargeAttack(party.chargeAttack) setChargeAttack(party.details.chargeAttack)
setClearTime(party.clearTime) setClearTime(party.details.clearTime)
if (party.turnCount !== undefined) setTurnCount(party.turnCount) if (party.details.turnCount !== undefined)
if (party.buttonCount !== undefined) setButtonCount(party.buttonCount) setTurnCount(party.details.turnCount)
if (party.chainCount !== undefined) setChainCount(party.chainCount) if (party.details.buttonCount !== undefined)
setButtonCount(party.details.buttonCount)
if (party.details.chainCount !== undefined)
setChainCount(party.details.chainCount)
} }
async function updateDetails(event: React.MouseEvent) { async function updateDetails(event: React.MouseEvent) {
@ -433,7 +437,7 @@ const EditPartyModal = ({
const nameField = ( const nameField = (
<Input <Input
name="name" name="name"
placeholder="Name your team" placeholder={t('modals.edit_team.placeholders.name')}
autoFocus={true} autoFocus={true}
value={name} value={name}
maxLength={50} maxLength={50}
@ -476,15 +480,6 @@ const EditPartyModal = ({
/> />
) )
const editorField = (
<Editor
bound={true}
content={description}
editable={true}
onUpdate={handleEditorUpdate}
/>
)
const chargeAttackField = ( const chargeAttackField = (
<SwitchTableField <SwitchTableField
name="charge_attack" name="charge_attack"

View file

@ -18,15 +18,17 @@ import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState' import { appState, initialAppState } from '~utils/appState'
import { getLocalId } from '~utils/localId' import { getLocalId } from '~utils/localId'
import { GridType } from '~utils/enums' import { GridType } from '~utils/enums'
import * as PartyTransformer from '~transformers/PartyTransformer'
import { retrieveCookies } from '~utils/retrieveCookies' import { retrieveCookies } from '~utils/retrieveCookies'
import { setEditKey, storeEditKey, unsetEditKey } from '~utils/userToken' import { setEditKey, storeEditKey, unsetEditKey } from '~utils/userToken'
import type { CharacterOverMastery, DetailsObject } from '~types' import type { CharacterOverMastery, DetailsObject } from '~types'
import { ElementMap } from '~utils/elements'
// Props // Props
interface Props { interface Props {
new?: boolean new?: boolean
team?: Party party?: Party
selectedTab: GridType selectedTab: GridType
raidGroups: RaidGroup[] raidGroups: RaidGroup[]
handleTabChanged: (value: string) => void handleTabChanged: (value: string) => void
@ -57,10 +59,10 @@ const Party = (props: Props) => {
// Reset state on first load // Reset state on first load
useEffect(() => { useEffect(() => {
const resetState = clonedeep(initialAppState) const resetState = clonedeep(initialAppState)
appState.grid = resetState.grid appState.party.grid = resetState.party.grid
if (props.team) { if (props.party) {
storeParty(props.team) storeParty(props.party)
setUpdatedParty(props.team) setUpdatedParty(props.party)
} }
}, []) }, [])
@ -85,20 +87,20 @@ const Party = (props: Props) => {
if (props.new) editable = true if (props.new) editable = true
if (accountData && props.team && !props.new) { if (accountData && props.party && !props.new) {
if (accountData.token) { if (accountData.token) {
// Authenticated // Authenticated
if (props.team.user && accountData.userId === props.team.user.id) { if (props.party.user && accountData.userId === props.party.user.id) {
editable = true editable = true
} }
} else { } else {
// Not authenticated // Not authenticated
if (!props.team.user && accountData.userId === props.team.local_id) { if (!props.party.user && accountData.userId === props.party.localId) {
// Set editable // Set editable
editable = true editable = true
// Also set edit key header // Also set edit key header
setEditKey(props.team.id, props.team.user) setEditKey(props.party.id, props.party.user)
} }
} }
} }
@ -122,9 +124,9 @@ const Party = (props: Props) => {
async function updateParty(details: DetailsObject) { async function updateParty(details: DetailsObject) {
const payload = formatDetailsObject(details) const payload = formatDetailsObject(details)
if (props.team && props.team.id) { if (props.party && props.party.id) {
return await api.endpoints.parties return await api.endpoints.parties
.update(props.team.id, payload) .update(props.party.id, payload)
.then((response) => { .then((response) => {
storeParty(response.data.party) storeParty(response.data.party)
setUpdatedParty(response.data.party) setUpdatedParty(response.data.party)
@ -142,7 +144,7 @@ const Party = (props: Props) => {
// Methods: Updating the party's details // Methods: Updating the party's details
async function updateDetails(details: DetailsObject) { async function updateDetails(details: DetailsObject) {
if (!props.team) return await createParty(details) if (!props.party) return await createParty(details)
else return await updateParty(details) else return await updateParty(details)
} }
@ -180,11 +182,11 @@ const Party = (props: Props) => {
} }
function checkboxChanged(enabled: boolean) { function checkboxChanged(enabled: boolean) {
appState.party.extra = enabled appState.party.details.extra = enabled
// Only save if this is a saved party // Only save if this is a saved party
if (props.team && props.team.id) { if (props.party && props.party.id) {
api.endpoints.parties.update(props.team.id, { api.endpoints.parties.update(props.party.id, {
party: { extra: enabled }, party: { extra: enabled },
}) })
} }
@ -203,7 +205,7 @@ const Party = (props: Props) => {
guidebook3_id: position === 3 ? id : undefined, guidebook3_id: position === 3 ? id : undefined,
} }
if (props.team && props.team.id) { if (props.party && props.party.id) {
updateParty(details) updateParty(details)
} else { } else {
createParty(details) createParty(details)
@ -214,16 +216,16 @@ const Party = (props: Props) => {
function remixTeam() { function remixTeam() {
// setOriginalName(partySnapshot.name ? partySnapshot.name : t('no_title')) // setOriginalName(partySnapshot.name ? partySnapshot.name : t('no_title'))
if (props.team && props.team.shortcode) { if (props.party && props.party.shortcode) {
const body = { local_id: getLocalId() } const body = { local_id: getLocalId() }
api api
.remix({ shortcode: props.team.shortcode, body: body }) .remix({ shortcode: props.party.shortcode, body: body })
.then((response) => { .then((response) => {
const remix = response.data.party const remix = PartyTransformer.toObject(response.data.party)
// Store the edit key in local storage // Store the edit key in local storage
if (remix.edit_key) { if (remix.editKey) {
storeEditKey(remix.id, remix.edit_key) storeEditKey(remix.id, remix.editKey)
setEditKey(remix.id, remix.user) setEditKey(remix.id, remix.user)
} }
@ -235,9 +237,9 @@ const Party = (props: Props) => {
// Deleting the party // Deleting the party
function deleteTeam() { function deleteTeam() {
if (props.team && editable) { if (props.party && editable) {
api.endpoints.parties api.endpoints.parties
.destroy({ id: props.team.id }) .destroy({ id: props.party.id })
.then(() => { .then(() => {
// Push to route // Push to route
if (cookies && cookies.account.username) { if (cookies && cookies.account.username) {
@ -267,36 +269,38 @@ const Party = (props: Props) => {
appState.party.name = team.name appState.party.name = team.name
appState.party.description = team.description ? team.description : '' appState.party.description = team.description ? team.description : ''
appState.party.raid = team.raid appState.party.raid = team.raid
appState.party.updated_at = team.updated_at appState.party.protagonist.job = team.protagonist.job
appState.party.job = team.job appState.party.protagonist.skills = team.protagonist.skills
appState.party.jobSkills = team.job_skills appState.party.protagonist.accessory = team.protagonist.accessory
appState.party.accessory = team.accessory appState.party.protagonist.masterLevel = team.protagonist.master_level
appState.party.protagonist.ultimateMastery =
appState.party.chargeAttack = team.charge_attack team.protagonist.ultimate_mastery
appState.party.fullAuto = team.full_auto appState.party.details.chargeAttack = team.charge_attack
appState.party.autoGuard = team.auto_guard appState.party.details.fullAuto = team.details.full_auto
appState.party.autoSummon = team.auto_summon appState.party.details.autoGuard = team.details.auto_guard
appState.party.clearTime = team.clear_time appState.party.details.autoSummon = team.details.auto_summon
appState.party.buttonCount = appState.party.details.clearTime = team.details.clear_time
team.button_count !== null ? team.button_count : undefined appState.party.details.buttonCount =
appState.party.chainCount = team.details.button_count !== null ? team.details.button_count : undefined
team.chain_count !== null ? team.chain_count : undefined appState.party.details.chainCount =
appState.party.turnCount = team.details.chain_count !== null ? team.details.chain_count : undefined
team.turn_count !== null ? team.turn_count : undefined appState.party.details.turnCount =
team.details.turn_count !== null ? team.details.turn_count : undefined
appState.party.id = team.id appState.party.id = team.id
appState.party.shortcode = team.shortcode appState.party.shortcode = team.shortcode
appState.party.extra = team.extra appState.party.details.extra = team.details.extra
appState.party.guidebooks = team.guidebooks appState.party.guidebooks = team.guidebooks
appState.party.user = team.user appState.party.user = team.user
appState.party.favorited = team.favorited appState.party.social.favorited = team.social.favorited
appState.party.remix = team.remix appState.party.social.remix = team.social.remix
appState.party.remixes = team.remixes appState.party.social.remixes = team.social.remixes
appState.party.sourceParty = team.source_party appState.party.social.sourceParty = team.social.sourceParty
appState.party.created_at = team.created_at appState.party.timestamps.createdAt = team.created_at
appState.party.updated_at = team.updated_at appState.party.timestamps.updatedAt = team.updated_at
appState.party.detailsVisible = false appState.party.grid = team.grid
console.log(team.grid)
// Store the edit key in local storage // Store the edit key in local storage
if (team.edit_key) { if (team.edit_key) {
@ -304,11 +308,6 @@ const Party = (props: Props) => {
setEditKey(team.id, team.user) setEditKey(team.id, team.user)
} }
// Populate state
storeCharacters(team.characters)
storeWeapons(team.weapons)
storeSummons(team.summons)
// Create a string to send the user back to the tab they're currently on // Create a string to send the user back to the tab they're currently on
let tab = '' let tab = ''
if (props.selectedTab === GridType.Character) { if (props.selectedTab === GridType.Character) {
@ -326,53 +325,56 @@ const Party = (props: Props) => {
} }
const storeCharacters = (list: Array<GridCharacter>) => { const storeCharacters = (list: Array<GridCharacter>) => {
list.forEach((object: GridCharacter) => { // list.forEach((object: GridCharacter) => {
let character = clonedeep(object) // let character = clonedeep(object)
// if (character.mastery.overMastery) {
if (character.over_mastery) { // const overMastery: CharacterOverMastery = {
const overMastery: CharacterOverMastery = { // 1: object.mastery.overMastery[0],
1: object.over_mastery[0], // 2: object.mastery.overMastery[1],
2: object.over_mastery[1], // 3: object.mastery.overMastery[2],
3: object.over_mastery[2], // 4: object.mastery.overMastery[3],
4: object.over_mastery[3], // }
} // character.mastery.overMastery = overMastery
// }
character.over_mastery = overMastery // if (character.position != null) {
} // appState.grid.characters[object.position] = character
// }
if (character.position != null) { // })
appState.grid.characters[object.position] = character
}
})
} }
const storeWeapons = (list: Array<GridWeapon>) => { const storeWeapons = (
list.forEach((gridObject: GridWeapon) => { allWeapons: Array<GridWeapon>,
if (gridObject.mainhand) { mainWeapon?: GridWeapon
appState.grid.weapons.mainWeapon = gridObject ) => {
appState.party.element = gridObject.object.element // appState.grid.weapons.mainWeapon = mainWeapon
} else if (!gridObject.mainhand && gridObject.position !== null) { // list.forEach((gridObject: GridWeapon) => {
let weapon = clonedeep(gridObject) // if (gridObject.mainhand) {
if (weapon.object.element === 0 && weapon.element < 1) // appState.grid.weapons.mainWeapon = gridObject
weapon.element = gridObject.object.element // appState.party.element = gridObject.object.element
// } else if (!gridObject.mainhand && gridObject.position !== null) {
appState.grid.weapons.allWeapons[gridObject.position] = weapon // let weapon = clonedeep(gridObject)
} // if (
}) // weapon.object.element === ElementMap.null &&
// weapon.element === ElementMap.null
// )
// weapon.element = gridObject.object.element
// appState.grid.weapons.allWeapons[gridObject.position] = weapon
// }
// })
} }
const storeSummons = (list: Array<GridSummon>) => { const storeSummons = (list: Array<GridSummon>) => {
list.forEach((gridObject: GridSummon) => { // list.forEach((gridObject: GridSummon) => {
if (gridObject.main) appState.grid.summons.mainSummon = gridObject // if (gridObject.main) appState.grid.summons.mainSummon = gridObject
else if (gridObject.friend) // else if (gridObject.friend)
appState.grid.summons.friendSummon = gridObject // appState.grid.summons.friendSummon = gridObject
else if ( // else if (
!gridObject.main && // !gridObject.main &&
!gridObject.friend && // !gridObject.friend &&
gridObject.position != null // gridObject.position != null
) // )
appState.grid.summons.allSummons[gridObject.position] = gridObject // appState.grid.summons.allSummons[gridObject.position] = gridObject
}) // })
} }
// Methods: Navigating with segmented control // Methods: Navigating with segmented control
@ -403,8 +405,8 @@ const Party = (props: Props) => {
<WeaponGrid <WeaponGrid
new={props.new || false} new={props.new || false}
editable={editable} editable={editable}
weapons={props.team?.weapons} weapons={props.party?.grid.weapons}
guidebooks={props.team?.guidebooks} guidebooks={props.party?.guidebooks}
createParty={createParty} createParty={createParty}
pushHistory={props.pushHistory} pushHistory={props.pushHistory}
updateExtra={checkboxChanged} updateExtra={checkboxChanged}
@ -416,7 +418,7 @@ const Party = (props: Props) => {
<SummonGrid <SummonGrid
new={props.new || false} new={props.new || false}
editable={editable} editable={editable}
summons={props.team?.summons} summons={props.party?.grid.summons}
createParty={createParty} createParty={createParty}
pushHistory={props.pushHistory} pushHistory={props.pushHistory}
/> />
@ -426,7 +428,7 @@ const Party = (props: Props) => {
<CharacterGrid <CharacterGrid
new={props.new || false} new={props.new || false}
editable={editable} editable={editable}
characters={props.team?.characters} characters={props.party?.grid.characters}
createParty={createParty} createParty={createParty}
pushHistory={props.pushHistory} pushHistory={props.pushHistory}
/> />

View file

@ -16,7 +16,6 @@ import EditPartyModal from '../EditPartyModal'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import { youtube } from '~utils/youtube'
import type { DetailsObject } from 'types' import type { DetailsObject } from 'types'
@ -39,10 +38,7 @@ const PartyFooter = (props: Props) => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const router = useRouter() const router = useRouter()
const { party: partySnapshot } = useSnapshot(appState) const { party } = useSnapshot(appState)
const youtubeUrlRegex =
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
// State: Component // State: Component
const [currentSegment, setCurrentSegment] = useState(0) const [currentSegment, setCurrentSegment] = useState(0)
@ -55,69 +51,19 @@ const PartyFooter = (props: Props) => {
const [sanitizedDescription, setSanitizedDescription] = useState('') const [sanitizedDescription, setSanitizedDescription] = useState('')
useEffect(() => { useEffect(() => {
if (partySnapshot.description) { if (party.description) {
const purified = DOMPurify.sanitize(partySnapshot.description) const purified = DOMPurify.sanitize(party.description)
setSanitizedDescription(purified) setSanitizedDescription(purified)
} else { } else {
setSanitizedDescription('') setSanitizedDescription('')
} }
}, [partySnapshot.description]) }, [party.description])
// Extract the video IDs from the description
// const videoIds = extractYoutubeVideoIds(partySnapshot.description)
// Fetch the video titles for each ID
// const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))
// // Wait for all the video titles to be fetched
// Promise.all(fetchPromises).then((videoTitles) => {
// // Replace the video URLs in the description with LiteYoutubeEmbed elements
// const newDescription = reactStringReplace(
// partySnapshot.description,
// youtubeUrlRegex,
// (match, i) => (
// <LiteYouTubeEmbed
// key={`${match}-${i}`}
// id={match}
// title={videoTitles[i]}
// wrapperClass={styles.youtube}
// playerClass={styles.playerButton}
// />
// )
// )
// Update the state with the new description
async function fetchYoutubeData(videoId: string) {
return await youtube
.getVideoById(videoId, { maxResults: 1 })
.then((data) => data.items[0].snippet.localized.title)
}
// Methods: Navigation // Methods: Navigation
function goTo(shortcode?: string) { function goTo(shortcode?: string) {
if (shortcode) router.push(`/p/${shortcode}`) if (shortcode) router.push(`/p/${shortcode}`)
} }
function extractYoutubeVideoIds(text: string) {
// Initialize an array to store the video IDs
const videoIds = []
// Use the regular expression to find all the Youtube URLs in the text
let match
while ((match = youtubeUrlRegex.exec(text)) !== null) {
// Extract the video ID from the URL
const videoId = match[1]
// Add the video ID to the array, along with the character position of the URL
videoIds.push({
id: videoId,
url: match[0],
position: match.index,
})
}
// Return the array of video IDs
return videoIds
}
// Methods: Favorites // Methods: Favorites
function toggleFavorite(teamId: string, favorited: boolean) { function toggleFavorite(teamId: string, favorited: boolean) {
if (favorited) unsaveFavorite(teamId) if (favorited) unsaveFavorite(teamId)
@ -130,7 +76,7 @@ const PartyFooter = (props: Props) => {
const index = remixes.findIndex((p) => p.id === teamId) const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index] const party = remixes[index]
party.favorited = true party.social.favorited = true
let clonedParties = clonedeep(remixes) let clonedParties = clonedeep(remixes)
clonedParties[index] = party clonedParties[index] = party
@ -146,7 +92,7 @@ const PartyFooter = (props: Props) => {
const index = remixes.findIndex((p) => p.id === teamId) const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index] const party = remixes[index]
party.favorited = false party.social.favorited = false
let clonedParties = clonedeep(remixes) let clonedParties = clonedeep(remixes)
clonedParties[index] = party clonedParties[index] = party
@ -201,7 +147,9 @@ const PartyFooter = (props: Props) => {
selected={currentSegment === 1} selected={currentSegment === 1}
onClick={() => setCurrentSegment(1)} onClick={() => setCurrentSegment(1)}
> >
{t('footer.remixes.label', { count: partySnapshot?.remixes?.length })} {t('footer.remixes.label', {
count: party?.social.remixes?.length ?? 0,
})}
</Segment> </Segment>
</SegmentedControl> </SegmentedControl>
) )
@ -216,7 +164,7 @@ const PartyFooter = (props: Props) => {
key={props.party?.shortcode} key={props.party?.shortcode}
/> />
)} )}
{(!partySnapshot || !partySnapshot.description) && ( {(!party || !party.description) && (
<section className={styles.noDescription}> <section className={styles.noDescription}>
<h3>{t('footer.description.empty')}</h3> <h3>{t('footer.description.empty')}</h3>
{props.editable && ( {props.editable && (
@ -240,10 +188,10 @@ const PartyFooter = (props: Props) => {
const remixesSection = ( const remixesSection = (
<section className={styles.remixes}> <section className={styles.remixes}>
{partySnapshot?.remixes?.length > 0 && ( {party?.social.remixes?.length > 0 && (
<GridRepCollection>{renderRemixes()}</GridRepCollection> <GridRepCollection>{renderRemixes()}</GridRepCollection>
)} )}
{partySnapshot?.remixes?.length === 0 && ( {party?.social.remixes?.length === 0 && (
<div className={styles.noRemixes}> <div className={styles.noRemixes}>
<h3>{t('footer.remixes.empty')}</h3> <h3>{t('footer.remixes.empty')}</h3>
<Button <Button
@ -257,19 +205,19 @@ const PartyFooter = (props: Props) => {
) )
function renderRemixes() { function renderRemixes() {
return partySnapshot?.remixes.map((party, i) => { return party?.social.remixes.map((party, i) => {
return ( return (
<GridRep <GridRep
id={party.id} id={party.id}
shortcode={party.shortcode} shortcode={party.shortcode}
name={party.name} name={party.name}
createdAt={new Date(party.created_at)} createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid} raid={party.raid}
grid={party.weapons} weapons={party.grid.weapons}
user={party.user} user={party.user}
favorited={party.favorited} favorited={party.social.favorited}
fullAuto={party.full_auto} fullAuto={party.details.fullAuto}
autoGuard={party.auto_guard} autoGuard={party.details.autoGuard}
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}
@ -288,7 +236,7 @@ const PartyFooter = (props: Props) => {
<RemixTeamAlert <RemixTeamAlert
creator={props.editable} creator={props.editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')} name={party.name ? party.name : t('no_title')}
open={remixAlertOpen} open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange} onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixTeamCallback} remixCallback={remixTeamCallback}

View file

@ -32,7 +32,7 @@ const PartyHead = ({ party, meta }: Props) => {
raidName: party.raid ? party.raid.name[locale] : '', raidName: party.raid ? party.raid.name[locale] : '',
})} })}
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="/images/favicon.png" />
{/* OpenGraph */} {/* OpenGraph */}

View file

@ -59,24 +59,24 @@ const PartyHeader = (props: Props) => {
}) })
const linkClass = classNames({ const linkClass = classNames({
wind: party && party.element == 1, wind: party.element && party.element.slug === 'wind',
fire: party && party.element == 2, fire: party.element && party.element.slug === 'fire',
water: party && party.element == 3, water: party.element && party.element.slug === 'water',
earth: party && party.element == 4, earth: party.element && party.element.slug === 'earth',
dark: party && party.element == 5, dark: party.element && party.element.slug === 'dark',
light: party && party.element == 6, light: party.element && party.element.slug === 'light',
}) })
// Actions: Favorites // Actions: Favorites
function toggleFavorite() { function toggleFavorite() {
if (appState.party.favorited) unsaveFavorite() if (appState.party.social.favorited) unsaveFavorite()
else saveFavorite() else saveFavorite()
} }
function saveFavorite() { function saveFavorite() {
if (appState.party.id) if (appState.party.id)
api.saveTeam({ id: appState.party.id }).then((response) => { api.saveTeam({ id: appState.party.id }).then((response) => {
if (response.status == 201) appState.party.favorited = true if (response.status == 201) appState.party.social.favorited = true
}) })
else console.error('Failed to save team: No party ID') else console.error('Failed to save team: No party ID')
} }
@ -84,7 +84,7 @@ const PartyHeader = (props: Props) => {
function unsaveFavorite() { function unsaveFavorite() {
if (appState.party.id) if (appState.party.id)
api.unsaveTeam({ id: appState.party.id }).then((response) => { api.unsaveTeam({ id: appState.party.id }).then((response) => {
if (response.status == 200) appState.party.favorited = false if (response.status == 200) appState.party.social.favorited = false
}) })
else console.error('Failed to unsave team: No party ID') else console.error('Failed to unsave team: No party ID')
} }
@ -202,33 +202,33 @@ const PartyHeader = (props: Props) => {
// Render: Tokens // Render: Tokens
const chargeAttackToken = ( const chargeAttackToken = (
<Token active={party.chargeAttack} className="chargeAttack"> <Token active={party.details.chargeAttack} className="chargeAttack">
{`${t('party.details.labels.charge_attack')} ${ {`${t('party.details.labels.charge_attack')} ${
party.chargeAttack ? 'On' : 'Off' party.details.chargeAttack ? 'On' : 'Off'
}`} }`}
</Token> </Token>
) )
const fullAutoToken = ( const fullAutoToken = (
<Token active={party.fullAuto} className="fullAuto"> <Token active={party.details.fullAuto} className="fullAuto">
{`${t('party.details.labels.full_auto')} ${ {`${t('party.details.labels.full_auto')} ${
party.fullAuto ? 'On' : 'Off' party.details.fullAuto ? 'On' : 'Off'
}`} }`}
</Token> </Token>
) )
const autoGuardToken = ( const autoGuardToken = (
<Token active={party.autoGuard} className="autoGuard"> <Token active={party.details.autoGuard} className="autoGuard">
{`${t('party.details.labels.auto_guard')} ${ {`${t('party.details.labels.auto_guard')} ${
party.autoGuard ? 'On' : 'Off' party.details.autoGuard ? 'On' : 'Off'
}`} }`}
</Token> </Token>
) )
const autoSummonToken = ( const autoSummonToken = (
<Token active={party.autoSummon} className="autoSummon"> <Token active={party.details.autoSummon} className="autoSummon">
{`${t('party.details.labels.auto_summon')} ${ {`${t('party.details.labels.auto_summon')} ${
party.autoSummon ? 'On' : 'Off' party.details.autoSummon ? 'On' : 'Off'
}`} }`}
</Token> </Token>
) )
@ -236,31 +236,39 @@ const PartyHeader = (props: Props) => {
const turnCountToken = ( const turnCountToken = (
<Token> <Token>
{t('party.details.turns.with_count', { {t('party.details.turns.with_count', {
count: party.turnCount, count: party.details.turnCount,
})} })}
</Token> </Token>
) )
const buttonChainToken = () => { const buttonChainToken = () => {
if (party.buttonCount !== undefined || party.chainCount !== undefined) { if (
party.details.buttonCount !== undefined ||
party.details.chainCount !== undefined
) {
let string = '' let string = ''
if (party.buttonCount !== undefined) { if (party.details.buttonCount !== undefined) {
string += `${party.buttonCount}b` string += `${party.details.buttonCount}b`
} }
if (party.buttonCount === undefined && party.chainCount !== undefined) { if (
string += `0${t('party.details.suffix.buttons')}${party.chainCount}${t( party.details.buttonCount === undefined &&
party.details.chainCount !== undefined
) {
string += `0${t('party.details.suffix.buttons')}${
party.details.chainCount
}${t('party.details.suffix.chains')}`
} else if (
party.details.buttonCount !== undefined &&
party.details.chainCount !== undefined
) {
string += `${party.details.chainCount}${t(
'party.details.suffix.chains' 'party.details.suffix.chains'
)}` )}`
} else if ( } else if (
party.buttonCount !== undefined && party.details.buttonCount !== undefined &&
party.chainCount !== undefined party.details.chainCount === undefined
) {
string += `${party.chainCount}${t('party.details.suffix.chains')}`
} else if (
party.buttonCount !== undefined &&
party.chainCount === undefined
) { ) {
string += `0${t('party.details.suffix.chains')}` string += `0${t('party.details.suffix.chains')}`
} }
@ -270,8 +278,8 @@ const PartyHeader = (props: Props) => {
} }
const clearTimeToken = () => { const clearTimeToken = () => {
const minutes = Math.floor(party.clearTime / 60) const minutes = Math.floor(party.details.clearTime / 60)
const seconds = party.clearTime - minutes * 60 const seconds = party.details.clearTime - minutes * 60
let string = '' let string = ''
if (minutes > 0) if (minutes > 0)
@ -291,8 +299,8 @@ const PartyHeader = (props: Props) => {
{fullAutoToken} {fullAutoToken}
{autoSummonToken} {autoSummonToken}
{autoGuardToken} {autoGuardToken}
{party.turnCount !== undefined && turnCountToken} {party.details.turnCount !== undefined && turnCountToken}
{party.clearTime > 0 && clearTimeToken()} {party.details.clearTime > 0 && clearTimeToken()}
{buttonChainToken()} {buttonChainToken()}
</> </>
) )
@ -307,10 +315,12 @@ const PartyHeader = (props: Props) => {
className={classNames({ className={classNames({
save: true, save: true,
grow: true, grow: true,
saved: partySnapshot.favorited, saved: partySnapshot.social.favorited,
})} })}
text={ text={
appState.party.favorited ? t('buttons.saved') : t('buttons.save') appState.party.social.favorited
? t('buttons.saved')
: t('buttons.save')
} }
onClick={toggleFavorite} onClick={toggleFavorite}
/> />
@ -333,12 +343,13 @@ const PartyHeader = (props: Props) => {
const remixedButton = () => { const remixedButton = () => {
const tooltipString = const tooltipString =
party.remix && party.sourceParty party.social.remix && party.social.sourceParty
? t('tooltips.remix.source') ? t('tooltips.remix.source')
: t('tooltips.remix.deleted') : t('tooltips.remix.deleted')
const buttonAction = const buttonAction =
party.sourceParty && (() => goTo(party.sourceParty?.shortcode)) party.social.sourceParty &&
(() => goTo(party.social.sourceParty?.shortcode))
return ( return (
<Tooltip content={tooltipString}> <Tooltip content={tooltipString}>
@ -348,7 +359,7 @@ const PartyHeader = (props: Props) => {
leftAccessoryIcon={<RemixIcon />} leftAccessoryIcon={<RemixIcon />}
text={t('tokens.remix')} text={t('tokens.remix')}
size="small" size="small"
disabled={!party.sourceParty} disabled={!party.social.sourceParty}
onClick={buttonAction} onClick={buttonAction}
/> />
</Tooltip> </Tooltip>
@ -364,17 +375,17 @@ const PartyHeader = (props: Props) => {
<h1 className={party.name ? '' : styles.empty}> <h1 className={party.name ? '' : styles.empty}>
{party.name ? party.name : t('no_title')} {party.name ? party.name : t('no_title')}
</h1> </h1>
{party.remix && remixedButton()} {party.social.remix && remixedButton()}
</div> </div>
<div className={styles.attribution}> <div className={styles.attribution}>
{renderUserBlock()} {renderUserBlock()}
{appState.party.raid && linkedRaidBlock(appState.party.raid)} {appState.party.raid && linkedRaidBlock(appState.party.raid)}
{party.created_at != '' && ( {party.timestamps.createdAt != '' && (
<time <time
className={styles.lastUpdated} className={styles.lastUpdated}
dateTime={new Date(party.created_at).toString()} dateTime={new Date(party.timestamps.createdAt).toString()}
> >
{formatTimeAgo(new Date(party.created_at), locale)} {formatTimeAgo(new Date(party.timestamps.createdAt), locale)}
</time> </time>
)} )}
</div> </div>

View file

@ -12,6 +12,7 @@ import CharacterRep from '~components/reps/CharacterRep'
import WeaponRep from '~components/reps/WeaponRep' import WeaponRep from '~components/reps/WeaponRep'
import SummonRep from '~components/reps/SummonRep' import SummonRep from '~components/reps/SummonRep'
import { ElementMap } from '~utils/elements'
import { GridType } from '~utils/enums' import { GridType } from '~utils/enums'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -30,26 +31,27 @@ const PartySegmentedControl = (props: Props) => {
// Set up translations // Set up translations
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { party, grid } = useSnapshot(appState) const { party } = useSnapshot(appState)
const getElement = () => { const getElement = () => {
let element: number = 0 let element: GranblueElement
if (party.element == 0 && grid.weapons.mainWeapon) if (party.element === ElementMap.null && party.grid.weapons.mainWeapon)
element = grid.weapons.mainWeapon.element element = party.grid.weapons.mainWeapon?.element
else element = party.element else if (party.element) element = party.element
else element = ElementMap.null
switch (element) { switch (element) {
case 1: case ElementMap.wind:
return 'wind' return 'wind'
case 2: case ElementMap.fire:
return 'fire' return 'fire'
case 3: case ElementMap.water:
return 'water' return 'water'
case 4: case ElementMap.earth:
return 'earth' return 'earth'
case 5: case ElementMap.dark:
return 'dark' return 'dark'
case 6: case ElementMap.light:
return 'light' return 'light'
} }
} }
@ -64,12 +66,12 @@ const PartySegmentedControl = (props: Props) => {
onClick={props.onClick} onClick={props.onClick}
> >
<CharacterRep <CharacterRep
job={party.job} job={party.protagonist.job}
element={party.element} element={party.element}
gender={ gender={
accountState.account.user ? accountState.account.user.gender : 0 accountState.account.user ? accountState.account.user.gender : 0
} }
grid={grid.characters} grid={party.grid.characters}
/> />
</RepSegment> </RepSegment>
) )
@ -85,7 +87,7 @@ const PartySegmentedControl = (props: Props) => {
selected={props.selectedTab === GridType.Weapon} selected={props.selectedTab === GridType.Weapon}
onClick={props.onClick} onClick={props.onClick}
> >
<WeaponRep grid={grid.weapons} /> <WeaponRep grid={party.grid.weapons} />
</RepSegment> </RepSegment>
) )
} }
@ -100,7 +102,7 @@ const PartySegmentedControl = (props: Props) => {
selected={props.selectedTab === GridType.Summon} selected={props.selectedTab === GridType.Summon}
onClick={props.onClick} onClick={props.onClick}
> >
<SummonRep grid={grid.summons} /> <SummonRep grid={party.grid.summons} />
</RepSegment> </RepSegment>
) )
} }

View file

@ -3,6 +3,8 @@ import { useRouter } from 'next/router'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import classNames from 'classnames' import classNames from 'classnames'
import { ElementMap } from '~utils/elements'
import { Command, CommandGroup, CommandInput } from 'cmdk' import { Command, CommandGroup, CommandInput } from 'cmdk'
import Popover from '~components/common/Popover' import Popover from '~components/common/Popover'
import SegmentedControl from '~components/common/SegmentedControl' import SegmentedControl from '~components/common/SegmentedControl'
@ -50,7 +52,7 @@ const allRaidsOption: Raid = {
group: untitledGroup, group: untitledGroup,
slug: 'all', slug: 'all',
level: 0, level: 0,
element: 0, element: ElementMap.null,
} }
interface Props { interface Props {
@ -112,7 +114,6 @@ const RaidCombobox = (props: Props) => {
useEffect(() => { useEffect(() => {
const sections: [RaidGroup[], RaidGroup[], RaidGroup[]] = [[], [], []] const sections: [RaidGroup[], RaidGroup[], RaidGroup[]] = [[], [], []]
props.raidGroups.forEach((group) => { props.raidGroups.forEach((group) => {
if (group.section > 0) sections[group.section - 1].push(group) if (group.section > 0) sections[group.section - 1].push(group)
}) })
@ -354,7 +355,8 @@ const RaidCombobox = (props: Props) => {
function generateRaidItems(raids: Raid[]) { function generateRaidItems(raids: Raid[]) {
return raids return raids
.sort((a, b) => { .sort((a, b) => {
if (a.element > 0 && b.element > 0) return a.element - b.element if (a.element.id > 0 && b.element.id > 0)
return a.element.id - b.element.id
if (a.name.en.includes('NM') && b.name.en.includes('NM')) if (a.name.en.includes('NM') && b.name.en.includes('NM'))
return a.level - b.level return a.level - b.level
return a.name.en.localeCompare(b.name.en) return a.name.en.localeCompare(b.name.en)
@ -521,12 +523,12 @@ const RaidCombobox = (props: Props) => {
// Methods: Utility // Methods: Utility
// ---------------------------------------------- // ----------------------------------------------
const linkClass = classNames({ const linkClass = classNames({
wind: currentRaid && currentRaid.element == 1, wind: currentRaid && currentRaid.element == ElementMap.wind,
fire: currentRaid && currentRaid.element == 2, fire: currentRaid && currentRaid.element == ElementMap.fire,
water: currentRaid && currentRaid.element == 3, water: currentRaid && currentRaid.element == ElementMap.water,
earth: currentRaid && currentRaid.element == 4, earth: currentRaid && currentRaid.element == ElementMap.earth,
dark: currentRaid && currentRaid.element == 5, dark: currentRaid && currentRaid.element == ElementMap.dark,
light: currentRaid && currentRaid.element == 6, light: currentRaid && currentRaid.element == ElementMap.light,
}) })
// ---------------------------------------------- // ----------------------------------------------

View file

@ -3,13 +3,15 @@ import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import 'fix-date' import 'fix-date'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
import classNames from 'classnames' import classNames from 'classnames'
interface Props { interface Props {
job?: Job job?: Job
gender?: number gender?: number
element?: number element?: GranblueElement
grid: GridArray<GridCharacter> grid: GridArray<GridCharacter>
} }
@ -47,17 +49,17 @@ const CharacterRep = (props: Props) => {
// Convert element to string // Convert element to string
function numberToElement() { function numberToElement() {
switch (props.element) { switch (props.element) {
case 1: case ElementMap.wind:
return 'wind' return 'wind'
case 2: case ElementMap.fire:
return 'fire' return 'fire'
case 3: case ElementMap.water:
return 'water' return 'water'
case 4: case ElementMap.earth:
return 'earth' return 'earth'
case 5: case ElementMap.dark:
return 'dark' return 'dark'
case 6: case ElementMap.light:
return 'light' return 'light'
default: default:
return '' return ''
@ -91,21 +93,21 @@ const CharacterRep = (props: Props) => {
if (character && gridCharacter) { if (character && gridCharacter) {
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = '01' let suffix = '01'
if (gridCharacter.transcendence_step > 0) suffix = '04' if (gridCharacter.transcendenceStep > 0) suffix = '04'
else if (gridCharacter.uncap_level >= 5) suffix = '03' else if (gridCharacter.uncapLevel >= 5) suffix = '03'
else if (gridCharacter.uncap_level > 2) suffix = '02' else if (gridCharacter.uncapLevel > 2) suffix = '02'
if (character.element == 0) { if (character.element === ElementMap.null) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${props.element}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblueId}_${props.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblueId}_${suffix}.jpg`
} }
} }
return characters[position] ? ( return (
<img alt={characters[position]?.name[locale]} src={url} /> characters[position] && (
) : ( <img alt={characters[position]?.name[locale]} src={url} />
'' )
) )
} }

View file

@ -5,8 +5,8 @@ import styles from './index.module.scss'
interface Props { interface Props {
grid: { grid: {
mainSummon: GridSummon | undefined mainSummon?: GridSummon | undefined
friendSummon: GridSummon | undefined friendSummon?: GridSummon | undefined
allSummons: GridArray<GridSummon> allSummons: GridArray<GridSummon>
} }
} }
@ -70,31 +70,27 @@ const SummonRep = (props: Props) => {
if (mainSummon) { if (mainSummon) {
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = '' let suffix = ''
if (mainSummon.object.uncap.xlb && mainSummon.uncap_level == 6) { if (mainSummon.object.uncap.xlb && mainSummon.uncapLevel == 6) {
if ( if (
mainSummon.transcendence_step >= 1 && mainSummon.transcendenceStep >= 1 &&
mainSummon.transcendence_step < 5 mainSummon.transcendenceStep < 5
) { ) {
suffix = '_03' suffix = '_03'
} else if (mainSummon.transcendence_step === 5) { } else if (mainSummon.transcendenceStep === 5) {
suffix = '_04' suffix = '_04'
} }
} else if ( } else if (
upgradedSummons.indexOf(mainSummon.object.granblue_id.toString()) != upgradedSummons.indexOf(mainSummon.object.granblueId.toString()) !=
-1 && -1 &&
mainSummon.uncap_level == 5 mainSummon.uncapLevel == 5
) { ) {
suffix = '_02' suffix = '_02'
} }
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${mainSummon.object.granblue_id}${suffix}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${mainSummon.object.granblueId}${suffix}.jpg`
} }
return mainSummon ? ( return mainSummon && <img alt={mainSummon.object.name[locale]} src={url} />
<img alt={mainSummon.object.name[locale]} src={url} />
) : (
''
)
} }
function generateGridImage(position: number) { function generateGridImage(position: number) {
@ -123,29 +119,29 @@ const SummonRep = (props: Props) => {
if (summon && gridSummon) { if (summon && gridSummon) {
// Change the image based on the uncap level // Change the image based on the uncap level
let suffix = '' let suffix = ''
if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) { if (gridSummon.object.uncap.xlb && gridSummon.uncapLevel == 6) {
if ( if (
gridSummon.transcendence_step >= 1 && gridSummon.transcendenceStep >= 1 &&
gridSummon.transcendence_step < 5 gridSummon.transcendenceStep < 5
) { ) {
suffix = '_03' suffix = '_03'
} else if (gridSummon.transcendence_step === 5) { } else if (gridSummon.transcendenceStep === 5) {
suffix = '_04' suffix = '_04'
} }
} else if ( } else if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblueId.toString()) != -1 &&
gridSummon.uncap_level == 5 gridSummon.uncapLevel == 5
) { ) {
suffix = '_02' suffix = '_02'
} }
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblueId}${suffix}.jpg`
} }
return summons[position] ? ( return (
<img alt={summons[position]?.name[locale]} src={url} /> summons[position] && (
) : ( <img alt={summons[position]?.name[locale]} src={url} />
'' )
) )
} }

View file

@ -1,12 +1,11 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
import classNames from 'classnames'
interface Props { interface Props {
grid: { grid: {
mainWeapon: GridWeapon | undefined mainWeapon?: GridWeapon | undefined
allWeapons: GridArray<GridWeapon> allWeapons: GridArray<GridWeapon>
} }
} }
@ -53,14 +52,14 @@ const WeaponRep = (props: Props) => {
let url = '' let url = ''
if (mainhand && mainhand.object) { if (mainhand && mainhand.object) {
if (mainhand.object.element == 0 && mainhand.element) { if (mainhand.object.element === ElementMap.null && mainhand.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblue_id}_${mainhand.element}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblueId}_${mainhand.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblue_id}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblueId}.jpg`
} }
} }
return mainhand ? <img alt={mainhand.object.name[locale]} src={url} /> : '' return mainhand && <img alt={mainhand.object.name[locale]} src={url} />
} }
function generateGridImage(position: number) { function generateGridImage(position: number) {
@ -70,17 +69,17 @@ const WeaponRep = (props: Props) => {
const gridWeapon = grid[position] const gridWeapon = grid[position]
if (weapon && gridWeapon) { if (weapon && gridWeapon) {
if (weapon.element == 0 && gridWeapon.element) { if (weapon.element === ElementMap.null && gridWeapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}_${gridWeapon.element}.jpg`
} else { } else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg` url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}.jpg`
} }
} }
return weapons[position] ? ( return (
<img alt={weapons[position]?.name[locale]} src={url} /> weapons[position] && (
) : ( <img alt={weapons[position]?.name[locale]} src={url} />
'' )
) )
} }

View file

@ -15,6 +15,12 @@ import WeaponSearchFilterBar from '~components/weapon/WeaponSearchFilterBar'
import SummonSearchFilterBar from '~components/summon/SummonSearchFilterBar' import SummonSearchFilterBar from '~components/summon/SummonSearchFilterBar'
import JobSkillSearchFilterBar from '~components/job/JobSkillSearchFilterBar' import JobSkillSearchFilterBar from '~components/job/JobSkillSearchFilterBar'
import * as WeaponTransformer from '~transformers/WeaponTransformer'
import * as SummonTransformer from '~transformers/SummonTransformer'
import * as CharacterTransformer from '~transformers/CharacterTransformer'
import * as JobSkillTransformer from '~transformers/JobSkillTransformer'
import * as GuidebookTransformer from '~transformers/GuidebookTransformer'
import CharacterResult from '~components/character/CharacterResult' import CharacterResult from '~components/character/CharacterResult'
import WeaponResult from '~components/weapon/WeaponResult' import WeaponResult from '~components/weapon/WeaponResult'
import SummonResult from '~components/summon/SummonResult' import SummonResult from '~components/summon/SummonResult'
@ -126,7 +132,7 @@ const SearchModal = (props: Props) => {
if ( if (
!recents.find( !recents.find(
(item) => (item) =>
(item as Weapon).granblue_id === (result as Weapon).granblue_id (item as Weapon).granblueId === (result as Weapon).granblueId
) )
) { ) {
recents.unshift(result as Weapon) recents.unshift(result as Weapon)
@ -136,7 +142,7 @@ const SearchModal = (props: Props) => {
if ( if (
!recents.find( !recents.find(
(item) => (item) =>
(item as Summon).granblue_id === (result as Summon).granblue_id (item as Summon).granblueId === (result as Summon).granblueId
) )
) { ) {
recents.unshift(result as Summon) recents.unshift(result as Summon)
@ -270,13 +276,14 @@ const SearchModal = (props: Props) => {
const castResults: Weapon[] = results as Weapon[] const castResults: Weapon[] = results as Weapon[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Weapon) => { jsx = castResults.map((result: any) => {
const weapon = WeaponTransformer.toObject(result)
return ( return (
<WeaponResult <WeaponResult
key={result.id} key={weapon.id}
data={result} data={weapon}
onClick={() => { onClick={() => {
storeRecentResult(result) storeRecentResult(weapon)
}} }}
/> />
) )
@ -291,13 +298,14 @@ const SearchModal = (props: Props) => {
const castResults: Summon[] = results as Summon[] const castResults: Summon[] = results as Summon[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Summon) => { jsx = castResults.map((result: any) => {
const summon = SummonTransformer.toObject(result)
return ( return (
<SummonResult <SummonResult
key={result.id} key={summon.id}
data={result} data={summon}
onClick={() => { onClick={() => {
storeRecentResult(result) storeRecentResult(summon)
}} }}
/> />
) )
@ -312,13 +320,14 @@ const SearchModal = (props: Props) => {
const castResults: Character[] = results as Character[] const castResults: Character[] = results as Character[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Character) => { jsx = castResults.map((result: any) => {
const character = CharacterTransformer.toObject(result)
return ( return (
<CharacterResult <CharacterResult
key={result.id} key={character.id}
data={result} data={character}
onClick={() => { onClick={() => {
storeRecentResult(result) storeRecentResult(character)
}} }}
/> />
) )
@ -334,12 +343,13 @@ const SearchModal = (props: Props) => {
const castResults: JobSkill[] = results as JobSkill[] const castResults: JobSkill[] = results as JobSkill[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: JobSkill) => { jsx = castResults.map((result: JobSkill) => {
const jobSkill = JobSkillTransformer.toObject(result)
return ( return (
<JobSkillResult <JobSkillResult
key={result.id} key={jobSkill.id}
data={result} data={jobSkill}
onClick={() => { onClick={() => {
storeRecentResult(result) storeRecentResult(jobSkill)
}} }}
/> />
) )
@ -355,12 +365,13 @@ const SearchModal = (props: Props) => {
const castResults: Guidebook[] = results as Guidebook[] const castResults: Guidebook[] = results as Guidebook[]
if (castResults && Object.keys(castResults).length > 0) { if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Guidebook) => { jsx = castResults.map((result: Guidebook) => {
const guidebook = GuidebookTransformer.toObject(result)
return ( return (
<GuidebookResult <GuidebookResult
key={result.id} key={guidebook.id}
data={result} data={guidebook}
onClick={() => { onClick={() => {
storeRecentResult(result) storeRecentResult(guidebook)
}} }}
/> />
) )

View file

@ -14,6 +14,7 @@ import ExtraSummonsGrid from '~components/extra/ExtraSummonsGrid'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import * as GridSummonTransformer from '~transformers/GridSummonTransformer'
import type { DetailsObject, SearchableObject } from '~types' import type { DetailsObject, SearchableObject } from '~types'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -22,7 +23,11 @@ import styles from './index.module.scss'
interface Props { interface Props {
new: boolean new: boolean
editable: boolean editable: boolean
summons?: GridSummon[] summons?: {
mainSummon?: GridSummon
friendSummon?: GridSummon
allSummons: GridArray<GridSummon>
}
createParty: (details?: DetailsObject) => Promise<Party> createParty: (details?: DetailsObject) => Promise<Party>
pushHistory?: (path: string) => void pushHistory?: (path: string) => void
} }
@ -45,7 +50,7 @@ const SummonGrid = (props: Props) => {
const [errorAlertOpen, setErrorAlertOpen] = useState(false) const [errorAlertOpen, setErrorAlertOpen] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party } = useSnapshot(appState)
// Create a temporary state to store previous weapon uncap values and transcendence stages // Create a temporary state to store previous weapon uncap values and transcendence stages
const [previousUncapValues, setPreviousUncapValues] = useState<{ const [previousUncapValues, setPreviousUncapValues] = useState<{
@ -60,23 +65,23 @@ const SummonGrid = (props: Props) => {
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {} let initialPreviousUncapValues: { [key: number]: number } = {}
if (appState.grid.summons.mainSummon) if (appState.party.grid.summons.mainSummon)
initialPreviousUncapValues[-1] = initialPreviousUncapValues[-1] =
appState.grid.summons.mainSummon.uncap_level appState.party.grid.summons.mainSummon.uncapLevel
if (appState.grid.summons.friendSummon) if (appState.party.grid.summons.friendSummon)
initialPreviousUncapValues[6] = initialPreviousUncapValues[6] =
appState.grid.summons.friendSummon.uncap_level appState.party.grid.summons.friendSummon.uncapLevel
Object.values(appState.grid.summons.allSummons).map((o) => Object.values(appState.party.grid.summons.allSummons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0 o ? (initialPreviousUncapValues[o.position] = o.uncapLevel) : 0
) )
setPreviousUncapValues(initialPreviousUncapValues) setPreviousUncapValues(initialPreviousUncapValues)
}, [ }, [
appState.grid.summons.mainSummon, appState.party.grid.summons.mainSummon,
appState.grid.summons.friendSummon, appState.party.grid.summons.friendSummon,
appState.grid.summons.allSummons, appState.party.grid.summons.allSummons,
]) ])
// Methods: Adding an object from search // Methods: Adding an object from search
@ -115,11 +120,11 @@ const SummonGrid = (props: Props) => {
const position = data.meta['replaced'] const position = data.meta['replaced']
if (position == -1) { if (position == -1) {
appState.grid.summons.mainSummon = undefined appState.party.grid.summons.mainSummon = null
} else if (position == 6) { } else if (position == 6) {
appState.grid.summons.friendSummon = undefined appState.party.grid.summons.friendSummon = null
} else { } else {
appState.grid.summons.allSummons[position] = undefined appState.party.grid.summons.allSummons[position] = null
} }
} }
} }
@ -137,16 +142,20 @@ const SummonGrid = (props: Props) => {
position: position, position: position,
main: position == -1, main: position == -1,
friend: position == 6, friend: position == 6,
uncap_level: uncapLevel, uncapLevel: uncapLevel,
}, },
}) })
} }
function storeGridSummon(gridSummon: GridSummon) { function storeGridSummon(data: GridSummon) {
if (gridSummon.position == -1) appState.grid.summons.mainSummon = gridSummon const gridSummon = GridSummonTransformer.toObject(data)
if (gridSummon.position == -1)
appState.party.grid.summons.mainSummon = gridSummon
else if (gridSummon.position == 6) else if (gridSummon.position == 6)
appState.grid.summons.friendSummon = gridSummon appState.party.grid.summons.friendSummon = gridSummon
else appState.grid.summons.allSummons[gridSummon.position] = gridSummon else
appState.party.grid.summons.allSummons[gridSummon.position] = gridSummon
} }
// Methods: Updating uncap level // Methods: Updating uncap level
@ -212,15 +221,15 @@ const SummonGrid = (props: Props) => {
) )
const updateUncapLevel = (position: number, uncapLevel: number) => { const updateUncapLevel = (position: number, uncapLevel: number) => {
if (appState.grid.summons.mainSummon && position == -1) if (appState.party.grid.summons.mainSummon && position == -1)
appState.grid.summons.mainSummon.uncap_level = uncapLevel appState.party.grid.summons.mainSummon.uncapLevel = uncapLevel
else if (appState.grid.summons.friendSummon && position == 6) else if (appState.party.grid.summons.friendSummon && position == 6)
appState.grid.summons.friendSummon.uncap_level = uncapLevel appState.party.grid.summons.friendSummon.uncapLevel = uncapLevel
else { else {
const summon = appState.grid.summons.allSummons[position] const summon = appState.party.grid.summons.allSummons[position]
if (summon) { if (summon) {
summon.uncap_level = uncapLevel summon.uncapLevel = uncapLevel
appState.grid.summons.allSummons[position] = summon appState.party.grid.summons.allSummons[position] = summon
} }
} }
} }
@ -229,14 +238,15 @@ const SummonGrid = (props: Props) => {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues } let newPreviousValues = { ...previousUncapValues }
if (appState.grid.summons.mainSummon && position == -1) if (appState.party.grid.summons.mainSummon && position == -1)
newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
else if (appState.grid.summons.friendSummon && position == 6)
newPreviousValues[position] = newPreviousValues[position] =
appState.grid.summons.friendSummon.uncap_level appState.party.grid.summons.mainSummon.uncapLevel
else if (appState.party.grid.summons.friendSummon && position == 6)
newPreviousValues[position] =
appState.party.grid.summons.friendSummon.uncapLevel
else { else {
const summon = appState.grid.summons.allSummons[position] const summon = appState.party.grid.summons.allSummons[position]
newPreviousValues[position] = summon ? summon.uncap_level : 0 newPreviousValues[position] = summon ? summon.uncapLevel : 0
} }
setPreviousUncapValues(newPreviousValues) setPreviousUncapValues(newPreviousValues)
@ -254,18 +264,16 @@ const SummonGrid = (props: Props) => {
const payload = { const payload = {
summon: { summon: {
uncap_level: stage > 0 ? 6 : 5, uncapLevel: stage > 0 ? 6 : 5,
transcendence_step: stage, transcendence_step: stage,
}, },
} }
try { try {
if (stage != previousTranscendenceStages[position]) if (stage != previousTranscendenceStages[position])
await api.endpoints.grid_summons await api.updateTranscendence('summon', id, stage).then((response) => {
.update(id, payload) storeGridSummon(response.data.grid_summon)
.then((response) => { })
storeGridSummon(response.data.grid_summon)
})
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -318,15 +326,15 @@ const SummonGrid = (props: Props) => {
) )
const updateTranscendenceStage = (position: number, stage: number) => { const updateTranscendenceStage = (position: number, stage: number) => {
if (appState.grid.summons.mainSummon && position == -1) if (appState.party.grid.summons.mainSummon && position == -1)
appState.grid.summons.mainSummon.transcendence_step = stage appState.party.grid.summons.mainSummon.transcendenceStep = stage
else if (appState.grid.summons.friendSummon && position == 6) else if (appState.party.grid.summons.friendSummon && position == 6)
appState.grid.summons.friendSummon.transcendence_step = stage appState.party.grid.summons.friendSummon.transcendenceStep = stage
else { else {
const summon = appState.grid.summons.allSummons[position] const summon = appState.party.grid.summons.allSummons[position]
if (summon) { if (summon) {
summon.transcendence_step = stage summon.transcendenceStep = stage
appState.grid.summons.allSummons[position] = summon appState.party.grid.summons.allSummons[position] = summon
} }
} }
} }
@ -335,14 +343,15 @@ const SummonGrid = (props: Props) => {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues } let newPreviousValues = { ...previousUncapValues }
if (appState.grid.summons.mainSummon && position == -1) if (appState.party.grid.summons.mainSummon && position == -1)
newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
else if (appState.grid.summons.friendSummon && position == 6)
newPreviousValues[position] = newPreviousValues[position] =
appState.grid.summons.friendSummon.uncap_level appState.party.grid.summons.mainSummon.uncapLevel
else if (appState.party.grid.summons.friendSummon && position == 6)
newPreviousValues[position] =
appState.party.grid.summons.friendSummon.uncapLevel
else { else {
const summon = appState.grid.summons.allSummons[position] const summon = appState.party.grid.summons.allSummons[position]
newPreviousValues[position] = summon ? summon.uncap_level : 0 newPreviousValues[position] = summon ? summon.uncapLevel : 0
} }
setPreviousUncapValues(newPreviousValues) setPreviousUncapValues(newPreviousValues)
@ -354,11 +363,11 @@ const SummonGrid = (props: Props) => {
const data = response.data const data = response.data
if (data.position === -1) { if (data.position === -1) {
appState.grid.summons.mainSummon = undefined appState.party.grid.summons.mainSummon = null
} else if (data.position === 6) { } else if (data.position === 6) {
appState.grid.summons.friendSummon = undefined appState.party.grid.summons.friendSummon = null
} else { } else {
appState.grid.summons.allSummons[response.data.position] = undefined appState.party.grid.summons.allSummons[response.data.position] = null
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -382,7 +391,7 @@ const SummonGrid = (props: Props) => {
<div className="LabeledUnit"> <div className="LabeledUnit">
<div className={styles.label}>{t('summons.main')}</div> <div className={styles.label}>{t('summons.main')}</div>
<SummonUnit <SummonUnit
gridSummon={grid.summons.mainSummon} gridSummon={party.grid.summons.mainSummon}
editable={props.editable} editable={props.editable}
key="grid_main_summon" key="grid_main_summon"
position={-1} position={-1}
@ -406,7 +415,7 @@ const SummonGrid = (props: Props) => {
{t('summons.friend')} {t('summons.friend')}
</div> </div>
<SummonUnit <SummonUnit
gridSummon={grid.summons.friendSummon} gridSummon={party.grid.summons.friendSummon}
editable={props.editable} editable={props.editable}
key="grid_friend_summon" key="grid_friend_summon"
position={6} position={6}
@ -427,7 +436,7 @@ const SummonGrid = (props: Props) => {
return ( return (
<li key={`grid_unit_${i}`}> <li key={`grid_unit_${i}`}>
<SummonUnit <SummonUnit
gridSummon={grid.summons.allSummons[i]} gridSummon={party.grid.summons.allSummons[i]}
editable={props.editable} editable={props.editable}
position={i} position={i}
unitType={1} unitType={1}
@ -445,7 +454,7 @@ const SummonGrid = (props: Props) => {
const subAuraSummonElement = ( const subAuraSummonElement = (
<ExtraSummonsGrid <ExtraSummonsGrid
grid={grid.summons.allSummons} grid={party.grid.summons.allSummons}
editable={props.editable} editable={props.editable}
exists={false} exists={false}
offset={numSummons} offset={numSummons}

View file

@ -20,14 +20,9 @@ interface Props {
} }
const SummonHovercard = (props: Props) => { const SummonHovercard = (props: Props) => {
const router = useRouter()
const { t } = useTranslation('common') const { t } = useTranslation('common')
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light'] const tintElement = props.gridSummon.object.element.slug
const tintElement = Element[props.gridSummon.object.element]
function goTo() { function goTo() {
const urlSafeName = props.gridSummon.object.name.en.replaceAll(' ', '_') const urlSafeName = props.gridSummon.object.name.en.replaceAll(' ', '_')
@ -55,19 +50,19 @@ const SummonHovercard = (props: Props) => {
let suffix = '' let suffix = ''
if ( if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblueId.toString()) != -1 &&
props.gridSummon.uncap_level == 5 props.gridSummon.uncapLevel == 5
) { ) {
suffix = '_02' suffix = '_02'
} else if ( } else if (
props.gridSummon.object.uncap.xlb && props.gridSummon.object.uncap.xlb &&
props.gridSummon.transcendence_step > 0 props.gridSummon.transcendenceStep > 0
) { ) {
suffix = '_03' suffix = '_03'
} }
// Generate the correct source for the summon // Generate the correct source for the summon
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblueId}${suffix}.jpg`
} }
return imgSrc return imgSrc

View file

@ -11,8 +11,6 @@ interface Props {
onClick: () => void onClick: () => void
} }
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const SummonResult = (props: Props) => { const SummonResult = (props: Props) => {
const router = useRouter() const router = useRouter()
const locale = const locale =
@ -24,18 +22,21 @@ const SummonResult = (props: Props) => {
<li className={styles.result} onClick={props.onClick}> <li className={styles.result} onClick={props.onClick}>
<img <img
alt={summon.name[locale]} alt={summon.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}.jpg`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblueId}.jpg`}
/> />
<div className={styles.info}> <div className={styles.info}>
<h5>{summon.name[locale]}</h5> <h5>{summon.name[locale]}</h5>
<UncapIndicator <UncapIndicator
type="summon" type="summon"
flb={summon.uncap.flb} ulb={summon.uncap.ulb || false}
ulb={summon.uncap.ulb} flb={summon.uncap.flb || false}
xlb={summon.uncap.xlb || false}
uncapLevel={6}
transcendenceStage={5}
special={false} special={false}
/> />
<div className={styles.tags}> <div className={styles.tags}>
<WeaponLabelIcon labelType={Element[summon.element]} /> <WeaponLabelIcon labelType={summon.element.slug} />
</div> </div>
</div> </div>
</li> </li>

View file

@ -3,8 +3,6 @@ import { useTranslation } from 'next-i18next'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import SearchFilter from '~components/search/SearchFilter' import SearchFilter from '~components/search/SearchFilter'
import SearchFilterCheckboxItem from '~components/search/SearchFilterCheckboxItem' import SearchFilterCheckboxItem from '~components/search/SearchFilterCheckboxItem'

View file

@ -6,6 +6,7 @@ import classNames from 'classnames'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import * as GridSummonTransformer from '~transformers/GridSummonTransformer'
import Alert from '~components/common/Alert' import Alert from '~components/common/Alert'
import Button from '~components/common/Button' import Button from '~components/common/Button'
@ -26,7 +27,7 @@ import SettingsIcon from '~public/icons/Settings.svg'
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props { interface Props {
gridSummon: GridSummon | undefined gridSummon: GridSummon | null
unitType: 0 | 1 | 2 unitType: 0 | 1 | 2
position: number position: number
editable: boolean editable: boolean
@ -95,7 +96,7 @@ const SummonUnit = ({
} }
function handleQuickSummonClick() { function handleQuickSummonClick() {
if (gridSummon) updateQuickSummon(!gridSummon.quick_summon) if (gridSummon) updateQuickSummon(!gridSummon.quickSummon)
} }
// Methods: Handle open change // Methods: Handle open change
@ -125,13 +126,23 @@ const SummonUnit = ({
// If a user sets a quick summon while one is already set, // If a user sets a quick summon while one is already set,
// the previous one will be unset. // the previous one will be unset.
const gridSummons: GridSummon[] = response.data.summons const gridSummons: GridSummon[] = response.data.summons
const mainSummon = gridSummons.find((summon) =>
GridSummonTransformer.toObject(summon)
)
const friendSummon = gridSummons.find((summon) =>
GridSummonTransformer.toObject(summon)
)
for (const gridSummon of gridSummons) { for (const gridSummon of gridSummons) {
if (gridSummon.main) { if (gridSummon.main) {
appState.grid.summons.mainSummon = gridSummon appState.grid.summons.mainSummon = mainSummon
} else if (gridSummon.friend) { } else if (gridSummon.friend) {
appState.grid.summons.friendSummon = gridSummon appState.grid.summons.friendSummon = friendSummon
} else { } else {
appState.grid.summons.allSummons[gridSummon.position] = gridSummon appState.party.grid.summons.allSummons[gridSummon.position] = gridSummon
? GridSummonTransformer.toObject(gridSummon)
: null
} }
} }
} }
@ -157,8 +168,7 @@ const SummonUnit = ({
function generateImageUrl() { function generateImageUrl() {
let imgSrc = '' let imgSrc = ''
if (gridSummon) { if (gridSummon) {
const summon = gridSummon.object! const summon = gridSummon.object
const upgradedSummons = [ const upgradedSummons = [
'2040094000', '2040094000',
'2040100000', '2040100000',
@ -177,27 +187,28 @@ const SummonUnit = ({
] ]
let suffix = '' let suffix = ''
if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) { if (gridSummon.object.uncap.xlb && gridSummon.uncapLevel == 6) {
if ( if (
gridSummon.transcendence_step >= 1 && gridSummon.transcendenceStep &&
gridSummon.transcendence_step < 5 gridSummon.transcendenceStep >= 1 &&
gridSummon.transcendenceStep < 5
) { ) {
suffix = '_03' suffix = '_03'
} else if (gridSummon.transcendence_step === 5) { } else if (gridSummon.transcendenceStep === 5) {
suffix = '_04' suffix = '_04'
} }
} else if ( } else if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && upgradedSummons.indexOf(summon.granblueId) != -1 &&
gridSummon.uncap_level == 5 gridSummon.uncapLevel == 5
) { ) {
suffix = '_02' suffix = '_02'
} }
// Generate the correct source for the summon // Generate the correct source for the summon
if (unitType == 0 || unitType == 2) if (unitType == 0 || unitType == 2)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblue_id}${suffix}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.granblueId}${suffix}.jpg`
else else
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblueId}${suffix}.jpg`
} }
setImageUrl(imgSrc) setImageUrl(imgSrc)
@ -273,7 +284,7 @@ const SummonUnit = ({
if (gridSummon) { if (gridSummon) {
const classes = classNames({ const classes = classNames({
[styles.quickSummon]: true, [styles.quickSummon]: true,
[styles.empty]: !gridSummon.quick_summon, [styles.empty]: !gridSummon.quickSummon,
}) })
return <i className={classes} onClick={handleQuickSummonClick} /> return <i className={classes} onClick={handleQuickSummonClick} />
@ -327,8 +338,8 @@ const SummonUnit = ({
flb={gridSummon.object.uncap.flb || false} flb={gridSummon.object.uncap.flb || false}
xlb={gridSummon.object.uncap.xlb || false} xlb={gridSummon.object.uncap.xlb || false}
editable={editable} editable={editable}
uncapLevel={gridSummon.uncap_level} uncapLevel={gridSummon.uncapLevel}
transcendenceStage={gridSummon.transcendence_step} transcendenceStage={gridSummon.transcendenceStep}
position={gridSummon.position} position={gridSummon.position}
updateUncap={passUncapData} updateUncap={passUncapData}
updateTranscendence={passTranscendenceData} updateTranscendence={passTranscendenceData}

View file

@ -1,4 +1,4 @@
.Fragment { .fragment {
$degrees: 72deg; $degrees: 72deg;
$origWidth: 29px; $origWidth: 29px;
@ -28,11 +28,11 @@
cursor: pointer; cursor: pointer;
} }
&.Visible { &.visible {
opacity: 1; opacity: 1;
} }
&.Stage1 { &.stage1 {
top: 3px; top: 3px;
left: 18px; left: 18px;
@ -41,7 +41,7 @@
// } // }
} }
&.Stage2 { &.stage2 {
top: 10px; top: 10px;
left: 27px; left: 27px;
transform: rotate($degrees); transform: rotate($degrees);
@ -51,7 +51,7 @@
// } // }
} }
&.Stage3 { &.stage3 {
top: 21px; top: 21px;
left: 24px; left: 24px;
transform: rotate($degrees * 2); transform: rotate($degrees * 2);
@ -61,7 +61,7 @@
// } // }
} }
&.Stage4 { &.stage4 {
top: 21px; top: 21px;
left: 12px; left: 12px;
transform: rotate($degrees * 3); transform: rotate($degrees * 3);
@ -71,7 +71,7 @@
// } // }
} }
&.Stage5 { &.stage5 {
top: 10px; top: 10px;
left: 8px; left: 8px;
transform: rotate($degrees * 4); transform: rotate($degrees * 4);

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import classnames from 'classnames' import classNames from 'classnames'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -18,14 +18,14 @@ const TranscendenceFragment = ({
onClick, onClick,
onHover, onHover,
}: Props) => { }: Props) => {
const classes = classnames({ const classes = classNames({
Fragment: true, [styles.fragment]: true,
Visible: visible, [styles.visible]: visible,
Stage1: stage === 1, [styles.stage1]: stage === 1,
Stage2: stage === 2, [styles.stage2]: stage === 2,
Stage3: stage === 3, [styles.stage3]: stage === 3,
Stage4: stage === 4, [styles.stage4]: stage === 4,
Stage5: stage === 5, [styles.stage5]: stage === 5,
}) })
function handleClick() { function handleClick() {

View file

@ -1,11 +1,20 @@
.Transcendence.Popover { .transcendence {
align-items: center; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit-half; gap: $unit-half;
display: flex; align-items: center;
justify-content: center;
width: $unit-10x; width: $unit-10x;
height: $unit-10x; height: $unit-10x;
justify-content: center;
animation: scaleIn $duration-zoom ease-out;
background: var(--dialog-bg);
border-radius: $card-corner;
border: 0.5px solid rgba(0, 0, 0, 0.18);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.24);
outline: none;
padding: $unit;
transform-origin: var(--radix-popover-content-transform-origin);
z-index: 32; z-index: 32;
&.open { &.open {
@ -18,7 +27,50 @@
font-weight: $medium; font-weight: $medium;
} }
.Pending { .pending {
color: $yellow; color: $yellow;
} }
} }
@keyframes scaleIn {
0% {
opacity: 0;
transform: scale(0);
}
20% {
opacity: 0.2;
transform: scale(0.4);
}
40% {
opacity: 0.4;
transform: scale(0.8);
}
60% {
opacity: 0.6;
transform: scale(1);
}
65% {
opacity: 0.65;
transform: scale(1.1);
}
70% {
opacity: 0.7;
transform: scale(1);
}
75% {
opacity: 0.75;
transform: scale(0.98);
}
80% {
opacity: 0.8;
transform: scale(1.02);
}
90% {
opacity: 0.9;
transform: scale(0.96);
}
100% {
opacity: 1;
transform: scale(1);
}
}

View file

@ -2,8 +2,8 @@ import React, { PropsWithChildren, useEffect, useState } from 'react'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import classNames from 'classnames' import classNames from 'classnames'
import { Popover } from '@radix-ui/react-popover'
import { import {
Popover,
PopoverAnchor, PopoverAnchor,
PopoverContent, PopoverContent,
} from '~components/common/PopoverContent' } from '~components/common/PopoverContent'
@ -40,12 +40,8 @@ const TranscendencePopover = ({
const popoverRef = React.createRef<HTMLDivElement>() const popoverRef = React.createRef<HTMLDivElement>()
const classes = classNames({
Transcendence: true,
})
const levelClasses = classNames({ const levelClasses = classNames({
Pending: stage != currentStage, [styles.pending]: stage != currentStage,
}) })
useEffect(() => { useEffect(() => {
@ -77,16 +73,20 @@ const TranscendencePopover = ({
return ( return (
<Popover open={open} onOpenChange={onOpenChange}> <Popover open={open} onOpenChange={onOpenChange}>
<PopoverAnchor>{children}</PopoverAnchor> <PopoverAnchor>{children}</PopoverAnchor>
<PopoverContent className={classes} ref={popoverRef} tabIndex={tabIndex}> <PopoverContent
className={styles.transcendence}
ref={popoverRef}
tabIndex={tabIndex}
>
<TranscendenceStar <TranscendenceStar
className="Interactive Base" className="interactive base"
editable={true} editable={true}
interactive={true} interactive={true}
stage={stage} stage={stage}
onFragmentClick={handleFragmentClicked} onFragmentClick={handleFragmentClicked}
onFragmentHover={handleFragmentHovered} onFragmentHover={handleFragmentHovered}
/> />
<h4> <h4 className="name">
<span>{t('level')}&nbsp;</span> <span>{t('level')}&nbsp;</span>
<span className={levelClasses}>{baseLevel + 10 * currentStage}</span> <span className={levelClasses}>{baseLevel + 10 * currentStage}</span>
</h4> </h4>

View file

@ -38,7 +38,7 @@ const TranscendenceStar = ({
const starClasses = classnames({ const starClasses = classnames({
[styles.star]: true, [styles.star]: true,
[styles.immutable]: immutable, [styles.immutable]: immutable,
[styles.empty]: stage === 0, [styles.empty]: true,
[styles.stage1]: stage === 1, [styles.stage1]: stage === 1,
[styles.stage2]: stage === 2, [styles.stage2]: stage === 2,
[styles.stage3]: stage === 3, [styles.stage3]: stage === 3,
@ -46,9 +46,12 @@ const TranscendenceStar = ({
[styles.stage5]: stage === 5, [styles.stage5]: stage === 5,
}) })
const baseImageClasses = classnames(className, { const baseImageClasses = classnames(
[styles.figure]: true, {
}) [styles.figure]: true,
},
className?.split(' ').map((c) => styles[c])
)
useEffect(() => { useEffect(() => {
setVisibleStage(stage) setVisibleStage(stage)
@ -87,7 +90,7 @@ const TranscendenceStar = ({
onMouseLeave={interactive ? handleMouseLeave : () => {}} onMouseLeave={interactive ? handleMouseLeave : () => {}}
tabIndex={tabIndex} tabIndex={tabIndex}
> >
<div className="Fragments"> <div className={styles.fragments}>
{[...Array(NUM_FRAGMENTS)].map((e, i) => { {[...Array(NUM_FRAGMENTS)].map((e, i) => {
const loopStage = i + 1 const loopStage = i + 1
return interactive ? ( return interactive ? (

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import UncapStar from '~components/uncap/UncapStar' import UncapStar from '~components/uncap/UncapStar'
import TranscendencePopover from '~components/uncap/TranscendencePopover' import TranscendencePopover from '~components/uncap/TranscendencePopover'
import TranscendenceStar from '~components/uncap/TranscendenceStar' import TranscendenceStar from '~components/uncap/TranscendenceStar'
@ -82,7 +82,11 @@ const UncapIndicator = (props: Props) => {
return props.type === 'character' || props.type === 'summon' ? ( return props.type === 'character' || props.type === 'summon' ? (
<TranscendencePopover <TranscendencePopover
open={popoverOpen} open={popoverOpen}
stage={props.transcendenceStage ? props.transcendenceStage : 0} stage={
props.transcendenceStage && props.transcendenceStage !== null
? props.transcendenceStage
: 0
}
type={props.type} type={props.type}
onOpenChange={togglePopover} onOpenChange={togglePopover}
sendValue={sendTranscendenceStage} sendValue={sendTranscendenceStage}

View file

@ -39,7 +39,7 @@ const WeaponConflictModal = (props: Props) => {
}, [setOpen, props.open]) }, [setOpen, props.open])
function imageUrl(weapon?: Weapon) { function imageUrl(weapon?: Weapon) {
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${weapon?.granblue_id}.jpg` return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${weapon?.granblueId}.jpg`
} }
function openChange(open: boolean) { function openChange(open: boolean) {

View file

@ -18,16 +18,21 @@ import WeaponConflictModal from '~components/weapon/WeaponConflictModal'
import api from '~utils/api' import api from '~utils/api'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import * as GridWeaponTransformer from '~transformers/GridWeaponTransformer'
import type { DetailsObject, SearchableObject } from '~types' import type { DetailsObject, SearchableObject } from '~types'
import styles from './index.module.scss' import styles from './index.module.scss'
import { ElementMap } from '~utils/elements'
// Props // Props
interface Props { interface Props {
new: boolean new: boolean
editable: boolean editable: boolean
weapons?: GridWeapon[] weapons?: {
mainWeapon?: GridWeapon
allWeapons: GridArray<GridWeapon>
}
guidebooks?: GuidebookList guidebooks?: GuidebookList
createParty: (details: DetailsObject) => Promise<Party> createParty: (details: DetailsObject) => Promise<Party>
pushHistory?: (path: string) => void pushHistory?: (path: string) => void
@ -54,7 +59,7 @@ const WeaponGrid = (props: Props) => {
const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false) const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party } = useSnapshot(appState)
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
// Set up state for conflict management // Set up state for conflict management
@ -71,16 +76,19 @@ const WeaponGrid = (props: Props) => {
useEffect(() => { useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {} let initialPreviousUncapValues: { [key: number]: number } = {}
if (appState.grid.weapons.mainWeapon) if (appState.party.grid.weapons.mainWeapon)
initialPreviousUncapValues[-1] = initialPreviousUncapValues[-1] =
appState.grid.weapons.mainWeapon.uncap_level appState.party.grid.weapons.mainWeapon.uncapLevel
Object.values(appState.grid.weapons.allWeapons).map((o) => Object.values(appState.party.grid.weapons.allWeapons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0 o ? (initialPreviousUncapValues[o.position] = o.uncapLevel) : 0
) )
setPreviousUncapValues(initialPreviousUncapValues) setPreviousUncapValues(initialPreviousUncapValues)
}, [appState.grid.weapons.mainWeapon, appState.grid.weapons.allWeapons]) }, [
appState.party.grid.weapons.mainWeapon,
appState.party.grid.weapons.allWeapons,
])
// Methods: Adding an object from search // Methods: Adding an object from search
function receiveWeaponFromSearch(object: SearchableObject, position: number) { function receiveWeaponFromSearch(object: SearchableObject, position: number) {
@ -88,7 +96,7 @@ const WeaponGrid = (props: Props) => {
if (position == 1) appState.party.element = weapon.element if (position == 1) appState.party.element = weapon.element
if (!party.id) { if (!party.id) {
const payload: DetailsObject = { extra: party.extra } const payload: DetailsObject = { extra: party.details.extra }
props.createParty(payload).then((team) => { props.createParty(payload).then((team) => {
saveWeapon(team.id, weapon, position).then((response) => { saveWeapon(team.id, weapon, position).then((response) => {
if (response) storeGridWeapon(response.data.grid_weapon) if (response) storeGridWeapon(response.data.grid_weapon)
@ -143,10 +151,10 @@ const WeaponGrid = (props: Props) => {
const position = data.meta['replaced'] const position = data.meta['replaced']
if (position == -1) { if (position == -1) {
appState.grid.weapons.mainWeapon = undefined appState.party.grid.weapons.mainWeapon = null
appState.party.element = 0 appState.party.element = ElementMap.null
} else { } else {
appState.grid.weapons.allWeapons[position] = undefined appState.party.grid.weapons.allWeapons[position] = null
} }
} }
} }
@ -160,16 +168,17 @@ const WeaponGrid = (props: Props) => {
let post = false let post = false
if ( if (
position === -1 && position === -1 &&
(!appState.grid.weapons.mainWeapon || (!appState.party.grid.weapons.mainWeapon ||
(appState.grid.weapons.mainWeapon && (appState.party.grid.weapons.mainWeapon &&
appState.grid.weapons.mainWeapon.object.id !== weapon.id)) appState.party.grid.weapons.mainWeapon.object.id !== weapon.id))
) { ) {
post = true post = true
} else if ( } else if (
position !== -1 && position !== -1 &&
(!appState.grid.weapons.allWeapons[position] || (!appState.party.grid.weapons.allWeapons[position] ||
(appState.grid.weapons.allWeapons[position] && (appState.party.grid.weapons.allWeapons[position] &&
appState.grid.weapons.allWeapons[position]?.object.id !== weapon.id)) appState.party.grid.weapons.allWeapons[position]?.object.id !==
weapon.id))
) { ) {
post = true post = true
} }
@ -181,19 +190,20 @@ const WeaponGrid = (props: Props) => {
weapon_id: weapon.id, weapon_id: weapon.id,
position: position, position: position,
mainhand: position == -1, mainhand: position == -1,
uncap_level: uncapLevel, uncapLevel: uncapLevel,
}, },
}) })
} }
} }
function storeGridWeapon(gridWeapon: GridWeapon) { function storeGridWeapon(data: GridWeapon) {
const gridWeapon = GridWeaponTransformer.toObject(data)
if (gridWeapon.position === -1) { if (gridWeapon.position === -1) {
appState.grid.weapons.mainWeapon = gridWeapon appState.party.grid.weapons.mainWeapon = gridWeapon
appState.party.element = gridWeapon.object.element appState.party.element = gridWeapon.object.element
} else { } else {
// Store the grid unit at the correct position // Store the grid unit at the correct position
appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon appState.party.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
} }
} }
@ -209,11 +219,13 @@ const WeaponGrid = (props: Props) => {
.then((response) => { .then((response) => {
// Remove conflicting characters from state // Remove conflicting characters from state
conflicts.forEach((c) => { conflicts.forEach((c) => {
if (appState.grid.weapons.mainWeapon?.object.id === c.object.id) { if (
appState.grid.weapons.mainWeapon = undefined appState.party.grid.weapons.mainWeapon?.object.id === c.object.id
appState.party.element = 0 ) {
appState.party.grid.weapons.mainWeapon = null
appState.party.element = ElementMap.null
} else { } else {
appState.grid.weapons.allWeapons[c.position] = undefined appState.party.grid.weapons.allWeapons[c.position] = null
} }
}) })
@ -241,9 +253,9 @@ const WeaponGrid = (props: Props) => {
const data = response.data const data = response.data
if (data.position === -1) { if (data.position === -1) {
appState.grid.weapons.mainWeapon = undefined appState.party.grid.weapons.mainWeapon = null
} else { } else {
appState.grid.weapons.allWeapons[response.data.position] = undefined appState.party.grid.weapons.allWeapons[response.data.position] = null
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -307,13 +319,13 @@ const WeaponGrid = (props: Props) => {
const updateUncapLevel = (position: number, uncapLevel: number) => { const updateUncapLevel = (position: number, uncapLevel: number) => {
// console.log(`Updating uncap level at position ${position} to ${uncapLevel}`) // console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
if (appState.grid.weapons.mainWeapon && position == -1) if (appState.party.grid.weapons.mainWeapon && position == -1)
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel appState.party.grid.weapons.mainWeapon.uncapLevel = uncapLevel
else { else {
const weapon = appState.grid.weapons.allWeapons[position] const weapon = appState.party.grid.weapons.allWeapons[position]
if (weapon) { if (weapon) {
weapon.uncap_level = uncapLevel weapon.uncapLevel = uncapLevel
appState.grid.weapons.allWeapons[position] = weapon appState.party.grid.weapons.allWeapons[position] = weapon
} }
} }
} }
@ -322,12 +334,13 @@ const WeaponGrid = (props: Props) => {
// Save the current value in case of an unexpected result // Save the current value in case of an unexpected result
let newPreviousValues = { ...previousUncapValues } let newPreviousValues = { ...previousUncapValues }
if (appState.grid.weapons.mainWeapon && position == -1) { if (appState.party.grid.weapons.mainWeapon && position == -1) {
newPreviousValues[position] = appState.grid.weapons.mainWeapon.uncap_level newPreviousValues[position] =
appState.party.grid.weapons.mainWeapon.uncapLevel
} else { } else {
const weapon = appState.grid.weapons.allWeapons[position] const weapon = appState.party.grid.weapons.allWeapons[position]
if (weapon) { if (weapon) {
newPreviousValues[position] = weapon.uncap_level newPreviousValues[position] = weapon.uncapLevel
} else { } else {
newPreviousValues[position] = 0 newPreviousValues[position] = 0
} }
@ -339,13 +352,13 @@ const WeaponGrid = (props: Props) => {
// Methods: Convenience // Methods: Convenience
const displayExtraContainer = const displayExtraContainer =
props.editable || props.editable ||
appState.party.extra || appState.party.details.extra ||
Object.values(appState.party.guidebooks).every((el) => el === undefined) Object.values(appState.party.guidebooks).every((el) => el === undefined)
// Render: JSX components // Render: JSX components
const mainhandElement = ( const mainhandElement = (
<WeaponUnit <WeaponUnit
gridWeapon={appState.grid.weapons.mainWeapon} gridWeapon={appState.party.grid.weapons.mainWeapon}
editable={props.editable} editable={props.editable}
key="grid_mainhand" key="grid_mainhand"
position={-1} position={-1}
@ -358,13 +371,13 @@ const WeaponGrid = (props: Props) => {
const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => { const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => {
const itemClasses = classNames({ const itemClasses = classNames({
Empty: appState.grid.weapons.allWeapons[i] === undefined, Empty: appState.party.grid.weapons.allWeapons[i] === undefined,
}) })
return ( return (
<li className={itemClasses} key={`grid_unit_${i}`}> <li className={itemClasses} key={`grid_unit_${i}`}>
<WeaponUnit <WeaponUnit
gridWeapon={appState.grid.weapons.allWeapons[i]} gridWeapon={party.grid.weapons.allWeapons[i]}
editable={props.editable} editable={props.editable}
position={i} position={i}
unitType={1} unitType={1}
@ -377,13 +390,13 @@ const WeaponGrid = (props: Props) => {
}) })
const extraElement = () => { const extraElement = () => {
if (appState.party.raid && appState.party.raid.group.extra) { if (appState.party.raid && appState.party.raid.group?.extra) {
return ( return (
<ExtraContainer> <ExtraContainer>
<ExtraContainerItem title={t('extra_weapons')} className="weapons"> <ExtraContainerItem title={t('extra_weapons')} className="weapons">
{appState.party.raid && appState.party.raid.group.extra && ( {appState.party.raid && appState.party.raid.group.extra && (
<ExtraWeaponsGrid <ExtraWeaponsGrid
grid={appState.grid.weapons.allWeapons} grid={appState.party.grid.weapons.allWeapons}
editable={props.editable} editable={props.editable}
offset={numWeapons} offset={numWeapons}
removeWeapon={removeWeapon} removeWeapon={removeWeapon}
@ -411,30 +424,31 @@ const WeaponGrid = (props: Props) => {
} }
const conflictModal = () => { const conflictModal = () => {
return incoming && conflicts ? ( return (
<WeaponConflictModal incoming &&
open={modalOpen} conflicts && (
incomingWeapon={incoming} <WeaponConflictModal
conflictingWeapons={conflicts} open={modalOpen}
desiredPosition={position} incomingWeapon={incoming}
resolveConflict={resolveConflict} conflictingWeapons={conflicts}
resetConflict={resetConflict} desiredPosition={position}
/> resolveConflict={resolveConflict}
) : ( resetConflict={resetConflict}
'' />
)
) )
} }
const incompatibleAlert = () => { const incompatibleAlert = () => {
return showIncompatibleAlert ? ( return (
<Alert showIncompatibleAlert && (
open={showIncompatibleAlert} <Alert
cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)} open={showIncompatibleAlert}
cancelActionText={t('buttons.confirm')} cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)}
message={t('alert.incompatible_weapon')} cancelActionText={t('buttons.confirm')}
/> message={t('alert.incompatible_weapon')}
) : ( />
'' )
) )
} }

View file

@ -12,6 +12,7 @@ import HovercardHeader from '~components/HovercardHeader'
import Button from '~components/common/Button' import Button from '~components/common/Button'
import ax from '~data/ax' import ax from '~data/ax'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -36,7 +37,6 @@ const WeaponHovercard = (props: Props) => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const WeaponKeyNames: KeyNames = { const WeaponKeyNames: KeyNames = {
'2': { '2': {
en: 'Pendulum', en: 'Pendulum',
@ -57,9 +57,10 @@ const WeaponHovercard = (props: Props) => {
} }
const tintElement = const tintElement =
props.gridWeapon.object.element == 0 && props.gridWeapon.element props.gridWeapon.object.element === ElementMap.null &&
? Element[props.gridWeapon.element] props.gridWeapon.element
: Element[props.gridWeapon.object.element] ? props.gridWeapon.element.slug
: props.gridWeapon.object.element.slug
function goTo() { function goTo() {
const urlSafeName = props.gridWeapon.object.name.en.replaceAll(' ', '_') const urlSafeName = props.gridWeapon.object.name.en.replaceAll(' ', '_')
@ -76,7 +77,7 @@ const WeaponHovercard = (props: Props) => {
} }
const createPrimaryAxSkillString = () => { const createPrimaryAxSkillString = () => {
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1] const primaryAxSkills = ax[props.gridWeapon.object.axType - 1]
if (props.gridWeapon.ax) { if (props.gridWeapon.ax) {
const simpleAxSkill = props.gridWeapon.ax[0] const simpleAxSkill = props.gridWeapon.ax[0]
@ -93,7 +94,7 @@ const WeaponHovercard = (props: Props) => {
} }
const createSecondaryAxSkillString = () => { const createSecondaryAxSkillString = () => {
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1] const primaryAxSkills = ax[props.gridWeapon.object.axType - 1]
if (props.gridWeapon.ax) { if (props.gridWeapon.ax) {
const primarySimpleAxSkill = props.gridWeapon.ax[0] const primarySimpleAxSkill = props.gridWeapon.ax[0]
@ -135,27 +136,24 @@ const WeaponHovercard = (props: Props) => {
const keysSection = ( const keysSection = (
<section className={styles.weaponKeys}> <section className={styles.weaponKeys}>
{WeaponKeyNames[props.gridWeapon.object.series] ? ( {WeaponKeyNames[props.gridWeapon.object.series] && (
<h5 className={tintElement}> <h5 className={tintElement}>
{WeaponKeyNames[props.gridWeapon.object.series][locale]} {WeaponKeyNames[props.gridWeapon.object.series][locale]}
{locale === 'en' ? 's' : ''} {locale === 'en' ? 's' : ''}
</h5> </h5>
) : (
''
)} )}
{props.gridWeapon.weapon_keys {props.gridWeapon.weaponKeys &&
? Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => { Array.from(Array(props.gridWeapon.weaponKeys?.length)).map((x, i) => {
return ( return (
<div <div
className={styles.weaponKey} className={styles.weaponKey}
key={props.gridWeapon.weapon_keys![i].id} key={props.gridWeapon.weaponKeys![i].id}
> >
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span> <span>{props.gridWeapon.weaponKeys![i].name[locale]}</span>
</div> </div>
) )
}) })}
: ''}
</section> </section>
) )
@ -181,28 +179,26 @@ const WeaponHovercard = (props: Props) => {
</div> </div>
{props.gridWeapon.ax && {props.gridWeapon.ax &&
props.gridWeapon.ax[1].modifier && props.gridWeapon.ax[1].modifier &&
props.gridWeapon.ax[1].strength ? ( props.gridWeapon.ax[1].strength && (
<div <div
className={classNames({ className={classNames({
[styles.secondary]: true, [styles.secondary]: true,
[styles.axSkill]: true, [styles.axSkill]: true,
[styles.skill]: true, [styles.skill]: true,
})} })}
> >
<div className={styles.axImageWrapper}> <div className={styles.axImageWrapper}>
<img <img
alt="AX2" alt="AX2"
src={`/icons/ax/secondary_${ src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : '' props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`} }.png`}
/> />
</div>
<span>{createSecondaryAxSkillString()}</span>
</div> </div>
<span>{createSecondaryAxSkillString()}</span> )}
</div>
) : (
''
)}
</div> </div>
</section> </section>
) )
@ -233,8 +229,8 @@ const WeaponHovercard = (props: Props) => {
props.gridWeapon.ax[0].strength !== undefined && props.gridWeapon.ax[0].strength !== undefined &&
axSection} axSection}
{props.gridWeapon.awakening && awakeningSection} {props.gridWeapon.awakening && awakeningSection}
{props.gridWeapon.weapon_keys && {props.gridWeapon.weaponKeys &&
props.gridWeapon.weapon_keys.length > 0 && props.gridWeapon.weaponKeys.length > 0 &&
keysSection} keysSection}
{wikiButton} {wikiButton}
</HovercardContent> </HovercardContent>

View file

@ -35,7 +35,7 @@ const gauphNames = [
const emptyWeaponKey: WeaponKey = { const emptyWeaponKey: WeaponKey = {
id: 'no-key', id: 'no-key',
granblue_id: '-1', granblueId: '-1',
series: 0, series: 0,
slot: 0, slot: 0,
slug: '', slug: '',
@ -104,7 +104,7 @@ const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
<SelectItem <SelectItem
key={i} key={i}
value={item.id} value={item.id}
data-granblue-id={item.granblue_id} data-granblue-id={item.granblueId}
> >
{item.name.en} {item.name.en}
</SelectItem> </SelectItem>

View file

@ -17,6 +17,7 @@ import WeaponKeySelect from '~components/weapon/WeaponKeySelect'
import Button from '~components/common/Button' import Button from '~components/common/Button'
import { NO_AWAKENING } from '~data/awakening' import { NO_AWAKENING } from '~data/awakening'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -63,7 +64,7 @@ const WeaponModal = ({
// State: Data // State: Data
const [element, setElement] = useState<number>(0) const [element, setElement] = useState<GranblueElement>(ElementMap.null)
const [awakening, setAwakening] = useState<Awakening>() const [awakening, setAwakening] = useState<Awakening>()
const [awakeningLevel, setAwakeningLevel] = useState(1) const [awakeningLevel, setAwakeningLevel] = useState(1)
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1) const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
@ -84,8 +85,8 @@ const WeaponModal = ({
useEffect(() => { useEffect(() => {
setElement(gridWeapon.element) setElement(gridWeapon.element)
if (gridWeapon.weapon_keys) { if (gridWeapon.weaponKeys) {
gridWeapon.weapon_keys.forEach((key) => { gridWeapon.weaponKeys.forEach((key) => {
if (key.slot + 1 === 1) { if (key.slot + 1 === 1) {
setWeaponKey1(key) setWeaponKey1(key)
} else if (key.slot + 1 === 2) { } else if (key.slot + 1 === 2) {
@ -111,8 +112,8 @@ const WeaponModal = ({
// Methods: Data retrieval // Methods: Data retrieval
// Receive values from ElementToggle // Receive values from ElementToggle
function receiveElementValue(elementId: number) { function receiveElementValue(element: GranblueElement) {
setElement(elementId) setElement(element)
} }
// Receive values from AXSelect // Receive values from AXSelect
@ -153,7 +154,8 @@ const WeaponModal = ({
function prepareObject() { function prepareObject() {
let object: GridWeaponObject = { weapon: {} } let object: GridWeaponObject = { weapon: {} }
if (gridWeapon.object.element == 0) object.weapon.element = element if (gridWeapon.object.element === ElementMap.null)
object.weapon.element = element.id
if ([2, 3, 17, 24].includes(gridWeapon.object.series) && weaponKey1) { if ([2, 3, 17, 24].includes(gridWeapon.object.series) && weaponKey1) {
object.weapon.weapon_key1_id = weaponKey1.id object.weapon.weapon_key1_id = weaponKey1.id
@ -165,7 +167,7 @@ const WeaponModal = ({
if (gridWeapon.object.series == 17 && weaponKey3) if (gridWeapon.object.series == 17 && weaponKey3)
object.weapon.weapon_key3_id = weaponKey3.id object.weapon.weapon_key3_id = weaponKey3.id
if (gridWeapon.object.ax && gridWeapon.object.ax_type > 0) { if (gridWeapon.object.ax && gridWeapon.object.axType > 0) {
object.weapon.ax_modifier1 = primaryAxModifier object.weapon.ax_modifier1 = primaryAxModifier
object.weapon.ax_modifier2 = secondaryAxModifier object.weapon.ax_modifier2 = secondaryAxModifier
object.weapon.ax_strength1 = primaryAxValue object.weapon.ax_strength1 = primaryAxValue
@ -215,18 +217,18 @@ const WeaponModal = ({
// Reset values // Reset values
setElement(gridWeapon.element) setElement(gridWeapon.element)
setWeaponKey1( setWeaponKey1(
gridWeapon.weapon_keys && gridWeapon.weapon_keys[0] gridWeapon.weaponKeys && gridWeapon.weaponKeys[0]
? gridWeapon.weapon_keys[0] ? gridWeapon.weaponKeys[0]
: undefined : undefined
) )
setWeaponKey2( setWeaponKey2(
gridWeapon.weapon_keys && gridWeapon.weapon_keys[1] gridWeapon.weaponKeys && gridWeapon.weaponKeys[1]
? gridWeapon.weapon_keys[1] ? gridWeapon.weaponKeys[1]
: undefined : undefined
) )
setWeaponKey3( setWeaponKey3(
gridWeapon.weapon_keys && gridWeapon.weapon_keys[2] gridWeapon.weaponKeys && gridWeapon.weaponKeys[2]
? gridWeapon.weapon_keys[2] ? gridWeapon.weaponKeys[2]
: undefined : undefined
) )
setAwakening(gridWeapon.awakening?.type) setAwakening(gridWeapon.awakening?.type)
@ -276,13 +278,13 @@ const WeaponModal = ({
if (weaponKey && weaponKey.id === 'no-key') weaponKey = undefined if (weaponKey && weaponKey.id === 'no-key') weaponKey = undefined
// If the key is empty and the gridWeapon has no keys, nothing has changed // If the key is empty and the gridWeapon has no keys, nothing has changed
if (weaponKey === undefined && !gridWeapon.weapon_keys) return false if (weaponKey === undefined && !gridWeapon.weaponKeys) return false
// If the key is not empty but the gridWeapon has no keys, the key has changed // If the key is not empty but the gridWeapon has no keys, the key has changed
if ( if (
weaponKey !== undefined && weaponKey !== undefined &&
gridWeapon.weapon_keys && gridWeapon.weaponKeys &&
gridWeapon.weapon_keys.length === 0 gridWeapon.weaponKeys.length === 0
) )
return true return true
@ -290,15 +292,15 @@ const WeaponModal = ({
// then the key has changed // then the key has changed
const weaponKeyChanged = const weaponKeyChanged =
weaponKey && weaponKey &&
gridWeapon.weapon_keys && gridWeapon.weaponKeys &&
gridWeapon.weapon_keys[index] && gridWeapon.weaponKeys[index] &&
weaponKey.id !== gridWeapon.weapon_keys[index].id weaponKey.id !== gridWeapon.weaponKeys[index].id
return weaponKeyChanged return weaponKeyChanged
} }
function weaponKeysChanged() { function weaponKeysChanged() {
if (!gridWeapon.weapon_keys) return false if (!gridWeapon.weaponKeys) return false
const weaponKey1Changed = weaponKeyChanged(0) const weaponKey1Changed = weaponKeyChanged(0)
const weaponKey2Changed = weaponKeyChanged(1) const weaponKey2Changed = weaponKeyChanged(1)
@ -415,7 +417,7 @@ const WeaponModal = ({
<section> <section>
<h3>{t('modals.weapon.subtitles.ax_skills')}</h3> <h3>{t('modals.weapon.subtitles.ax_skills')}</h3>
<AXSelect <AXSelect
axType={gridWeapon.object.ax_type} axType={gridWeapon.object.axType}
currentSkills={gridWeapon.ax} currentSkills={gridWeapon.ax}
onOpenChange={receiveAxOpen} onOpenChange={receiveAxOpen}
sendValidity={receiveValidity} sendValidity={receiveValidity}
@ -432,7 +434,7 @@ const WeaponModal = ({
awakening={gridWeapon.awakening?.type} awakening={gridWeapon.awakening?.type}
level={gridWeapon.awakening?.level} level={gridWeapon.awakening?.level}
defaultAwakening={NO_AWAKENING} defaultAwakening={NO_AWAKENING}
maxLevel={gridWeapon.object.max_awakening_level} maxLevel={gridWeapon.object.maxAwakeningLevel}
onOpenChange={receiveAwakeningOpen} onOpenChange={receiveAwakeningOpen}
sendValidity={receiveValidity} sendValidity={receiveValidity}
sendValues={receiveAwakeningValues} sendValues={receiveAwakeningValues}
@ -479,12 +481,12 @@ const WeaponModal = ({
title={gridWeapon.object.name[locale]} title={gridWeapon.object.name[locale]}
subtitle={t('modals.characters.title')} subtitle={t('modals.characters.title')}
image={{ image={{
src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${gridWeapon.object.granblue_id}.jpg`, src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-square/${gridWeapon.object.granblueId}.jpg`,
alt: gridWeapon.object.name[locale], alt: gridWeapon.object.name[locale],
}} }}
/> />
<section className={styles.mods}> <section className={styles.mods}>
{gridWeapon.object.element == 0 && elementSelect} {gridWeapon.object.element === ElementMap.null && elementSelect}
{[2, 3, 17, 24].includes(gridWeapon.object.series) && keySelect} {[2, 3, 17, 24].includes(gridWeapon.object.series) && keySelect}
{gridWeapon.object.ax && axSelect} {gridWeapon.object.ax && axSelect}
{gridWeapon.object.awakenings && awakeningSelect} {gridWeapon.object.awakenings && awakeningSelect}

View file

@ -11,7 +11,6 @@ interface Props {
onClick: () => void onClick: () => void
} }
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [ const Proficiency = [
'none', 'none',
'sword', 'sword',
@ -36,7 +35,7 @@ const WeaponResult = (props: Props) => {
<li className={styles.result} onClick={props.onClick}> <li className={styles.result} onClick={props.onClick}>
<img <img
alt={weapon.name[locale]} alt={weapon.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`} src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}.jpg`}
/> />
<div className={styles.info}> <div className={styles.info}>
<h5>{weapon.name[locale]}</h5> <h5>{weapon.name[locale]}</h5>
@ -47,7 +46,7 @@ const WeaponResult = (props: Props) => {
special={false} special={false}
/> />
<div className={styles.tags}> <div className={styles.tags}>
<WeaponLabelIcon labelType={Element[weapon.element]} /> <WeaponLabelIcon labelType={weapon.element.slug} />
<WeaponLabelIcon labelType={Proficiency[weapon.proficiency]} /> <WeaponLabelIcon labelType={Proficiency[weapon.proficiency]} />
</div> </div>
</div> </div>

View file

@ -3,8 +3,6 @@ import { useTranslation } from 'next-i18next'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import SearchFilter from '~components/search/SearchFilter' import SearchFilter from '~components/search/SearchFilter'
import SearchFilterCheckboxItem from '~components/search/SearchFilterCheckboxItem' import SearchFilterCheckboxItem from '~components/search/SearchFilterCheckboxItem'

View file

@ -24,13 +24,14 @@ import Button from '~components/common/Button'
import type { GridWeaponObject, SearchableObject } from '~types' import type { GridWeaponObject, SearchableObject } from '~types'
import ax from '~data/ax' import ax from '~data/ax'
import { ElementMap } from '~utils/elements'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
import SettingsIcon from '~public/icons/Settings.svg' import SettingsIcon from '~public/icons/Settings.svg'
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props { interface Props {
gridWeapon: GridWeapon | undefined gridWeapon: GridWeapon | null
unitType: 0 | 1 unitType: 0 | 1
position: number position: number
editable: boolean editable: boolean
@ -154,7 +155,10 @@ const WeaponUnit = ({
appState.party.element = gridWeapon.object.element appState.party.element = gridWeapon.object.element
} else if (!gridWeapon.mainhand && gridWeapon.position !== null) { } else if (!gridWeapon.mainhand && gridWeapon.position !== null) {
let weapon = clonedeep(gridWeapon) let weapon = clonedeep(gridWeapon)
if (weapon.object.element === 0 && weapon.element < 1) if (
weapon.object.element === ElementMap.null &&
weapon.element === ElementMap.null
)
weapon.element = gridWeapon.object.element weapon.element = gridWeapon.object.element
appState.grid.weapons.allWeapons[gridWeapon.position] = weapon appState.grid.weapons.allWeapons[gridWeapon.position] = weapon
@ -170,10 +174,10 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.ax && gridWeapon.object.ax &&
gridWeapon.object.ax_type > 0 && gridWeapon.object.axType > 0 &&
gridWeapon.ax gridWeapon.ax
) { ) {
const axOptions = ax[gridWeapon.object.ax_type - 1] const axOptions = ax[gridWeapon.object.axType - 1]
const weaponAxSkill: SimpleAxSkill = gridWeapon.ax[0] const weaponAxSkill: SimpleAxSkill = gridWeapon.ax[0]
let axSkill = axOptions.find((ax) => ax.id === weaponAxSkill.modifier) let axSkill = axOptions.find((ax) => ax.id === weaponAxSkill.modifier)
@ -196,15 +200,15 @@ const WeaponUnit = ({
const weapon = gridWeapon.object! const weapon = gridWeapon.object!
if (unitType == 0) { if (unitType == 0) {
if (gridWeapon.object.element == 0 && gridWeapon.element) if (gridWeapon.object.element === ElementMap.null && gridWeapon.element)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblue_id}_${gridWeapon.element}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${weapon.granblueId}_${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.granblueId}.jpg`
} else { } else {
if (gridWeapon.object.element == 0 && gridWeapon.element) if (gridWeapon.object.element === ElementMap.null && gridWeapon.element)
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg` imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}_${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.granblueId}.jpg`
} }
} }
@ -242,19 +246,15 @@ const WeaponUnit = ({
let altText = '' let altText = ''
// If there is a grid weapon, it is a Draconic Weapon and it has keys // If there is a grid weapon, it is a Draconic Weapon and it has keys
if ( if (gridWeapon && gridWeapon.object.series === 3 && gridWeapon.weaponKeys) {
gridWeapon && if (index === 0 && gridWeapon.weaponKeys[0]) {
gridWeapon.object.series === 3 && altText = `${gridWeapon.weaponKeys[0].name[locale]}`
gridWeapon.weapon_keys filename = `${gridWeapon.weaponKeys[0].slug}.png`
) { } else if (index === 1 && gridWeapon.weaponKeys[1]) {
if (index === 0 && gridWeapon.weapon_keys[0]) { altText = `${gridWeapon.weaponKeys[1].name[locale]}`
altText = `${gridWeapon.weapon_keys[0].name[locale]}`
filename = `${gridWeapon.weapon_keys[0].slug}.png`
} else if (index === 1 && gridWeapon.weapon_keys[1]) {
altText = `${gridWeapon.weapon_keys[1].name[locale]}`
const element = gridWeapon.object.element const element = gridWeapon.object.element
filename = `${gridWeapon.weapon_keys[1].slug}-${element}.png` filename = `${gridWeapon.weaponKeys[1].slug}-${element}.png`
} }
return ( return (
@ -273,10 +273,10 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.series === 3 && gridWeapon.object.series === 3 &&
gridWeapon.weapon_keys && gridWeapon.weaponKeys &&
gridWeapon.weapon_keys.length > 0 gridWeapon.weaponKeys.length > 0
) { ) {
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { for (let i = 0; i < gridWeapon.weaponKeys.length; i++) {
const image = telumaImage(i) const image = telumaImage(i)
if (image) images.push(image) if (image) images.push(image)
} }
@ -294,25 +294,25 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.series === 17 && gridWeapon.object.series === 17 &&
gridWeapon.weapon_keys gridWeapon.weaponKeys
) { ) {
if ( if (
gridWeapon.weapon_keys[index] && gridWeapon.weaponKeys[index] &&
(gridWeapon.weapon_keys[index].slot === 1 || (gridWeapon.weaponKeys[index].slot === 1 ||
gridWeapon.weapon_keys[index].slot === 2) gridWeapon.weaponKeys[index].slot === 2)
) { ) {
altText = `${gridWeapon.weapon_keys[index].name[locale]}` altText = `${gridWeapon.weaponKeys[index].name[locale]}`
filename = `${gridWeapon.weapon_keys[index].slug}.png` filename = `${gridWeapon.weaponKeys[index].slug}.png`
} else if ( } else if (
gridWeapon.weapon_keys[index] && gridWeapon.weaponKeys[index] &&
gridWeapon.weapon_keys[index].slot === 0 gridWeapon.weaponKeys[index].slot === 0
) { ) {
altText = `${gridWeapon.weapon_keys[index].name[locale]}` altText = `${gridWeapon.weaponKeys[index].name[locale]}`
const weapon = gridWeapon.object.proficiency const weapon = gridWeapon.object.proficiency
const suffix = `${weapon}` const suffix = `${weapon}`
filename = `${gridWeapon.weapon_keys[index].slug}-${suffix}.png` filename = `${gridWeapon.weaponKeys[index].slug}-${suffix}.png`
} }
} }
@ -331,10 +331,10 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.series === 17 && gridWeapon.object.series === 17 &&
gridWeapon.weapon_keys && gridWeapon.weaponKeys &&
gridWeapon.weapon_keys.length > 0 gridWeapon.weaponKeys.length > 0
) { ) {
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { for (let i = 0; i < gridWeapon.weaponKeys.length; i++) {
const image = ultimaImage(i) const image = ultimaImage(i)
if (image) images.push(image) if (image) images.push(image)
} }
@ -349,22 +349,18 @@ const WeaponUnit = ({
let altText = '' let altText = ''
// If there is a grid weapon, it is a Dark Opus Weapon and it has keys // If there is a grid weapon, it is a Dark Opus Weapon and it has keys
if ( if (gridWeapon && gridWeapon.object.series === 2 && gridWeapon.weaponKeys) {
gridWeapon &&
gridWeapon.object.series === 2 &&
gridWeapon.weapon_keys
) {
if ( if (
gridWeapon.weapon_keys[index] && gridWeapon.weaponKeys[index] &&
gridWeapon.weapon_keys[index].slot === 0 gridWeapon.weaponKeys[index].slot === 0
) { ) {
altText = `${gridWeapon.weapon_keys[index].name[locale]}` altText = `${gridWeapon.weaponKeys[index].name[locale]}`
filename = `${gridWeapon.weapon_keys[index].slug}.png` filename = `${gridWeapon.weaponKeys[index].slug}.png`
} else if ( } else if (
gridWeapon.weapon_keys[index] && gridWeapon.weaponKeys[index] &&
gridWeapon.weapon_keys[index].slot === 1 gridWeapon.weaponKeys[index].slot === 1
) { ) {
altText = `${gridWeapon.weapon_keys[index].name[locale]}` altText = `${gridWeapon.weaponKeys[index].name[locale]}`
const element = gridWeapon.object.element const element = gridWeapon.object.element
const mod = gridWeapon.object.name.en.includes('Repudiation') const mod = gridWeapon.object.name.en.includes('Repudiation')
@ -372,7 +368,7 @@ const WeaponUnit = ({
: 'magna' : 'magna'
const suffix = `${mod}-${element}` const suffix = `${mod}-${element}`
const weaponKey = gridWeapon.weapon_keys[index] const weaponKey = gridWeapon.weaponKeys[index]
if ( if (
[ [
@ -384,9 +380,9 @@ const WeaponUnit = ({
'chain-glorification', 'chain-glorification',
].includes(weaponKey.slug) ].includes(weaponKey.slug)
) { ) {
filename = `${gridWeapon.weapon_keys[index].slug}-${suffix}.png` filename = `${gridWeapon.weaponKeys[index].slug}-${suffix}.png`
} else { } else {
filename = `${gridWeapon.weapon_keys[index].slug}.png` filename = `${gridWeapon.weaponKeys[index].slug}.png`
} }
} }
@ -406,10 +402,10 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.series === 2 && gridWeapon.object.series === 2 &&
gridWeapon.weapon_keys && gridWeapon.weaponKeys &&
gridWeapon.weapon_keys.length > 0 gridWeapon.weaponKeys.length > 0
) { ) {
for (let i = 0; i < gridWeapon.weapon_keys.length; i++) { for (let i = 0; i < gridWeapon.weaponKeys.length; i++) {
const image = opusImage(i) const image = opusImage(i)
if (image) images.push(image) if (image) images.push(image)
} }
@ -424,7 +420,7 @@ const WeaponUnit = ({
if ( if (
gridWeapon && gridWeapon &&
gridWeapon.object.ax && gridWeapon.object.ax &&
gridWeapon.object.ax_type > 0 && gridWeapon.object.axType > 0 &&
gridWeapon.ax && gridWeapon.ax &&
axSkill axSkill
) { ) {
@ -592,7 +588,7 @@ const WeaponUnit = ({
type="weapon" type="weapon"
ulb={gridWeapon.object.uncap.ulb || false} ulb={gridWeapon.object.uncap.ulb || false}
flb={gridWeapon.object.uncap.flb || false} flb={gridWeapon.object.uncap.flb || false}
uncapLevel={gridWeapon.uncap_level} uncapLevel={gridWeapon.uncapLevel}
position={gridWeapon.position} position={gridWeapon.position}
updateUncap={passUncapData} updateUncap={passUncapData}
special={false} special={false}

View file

@ -4,7 +4,7 @@ export const NO_AWAKENING: Awakening = {
id: '0', id: '0',
name: { name: {
en: 'No awakening', en: 'No awakening',
jp: '覚醒なし', ja: '覚醒なし',
}, },
slug: 'no-awakening', slug: 'no-awakening',
order: 0, order: 0,

View file

@ -6,7 +6,7 @@ const ax: ItemSkill[][] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 0, id: 0,
granblue_id: '1589', granblueId: '1589',
slug: 'atk', slug: 'atk',
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
@ -19,7 +19,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
@ -32,7 +32,7 @@ const ax: ItemSkill[][] = [
ja: 'DA確率', ja: 'DA確率',
}, },
id: 5, id: 5,
granblue_id: '1596', granblueId: '1596',
slug: 'da', slug: 'da',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -45,7 +45,7 @@ const ax: ItemSkill[][] = [
ja: 'TA確率', ja: 'TA確率',
}, },
id: 6, id: 6,
granblue_id: '1597', granblueId: '1597',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -58,7 +58,7 @@ const ax: ItemSkill[][] = [
ja: 'アビ上限', ja: 'アビ上限',
}, },
id: 7, id: 7,
granblue_id: '1588', granblueId: '1588',
slug: 'skill-cap', slug: 'skill-cap',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -73,7 +73,7 @@ const ax: ItemSkill[][] = [
ja: '防御', ja: '防御',
}, },
id: 1, id: 1,
granblue_id: '1590', granblueId: '1590',
slug: 'def', slug: 'def',
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
@ -86,7 +86,7 @@ const ax: ItemSkill[][] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '1588', granblueId: '1588',
slug: 'hp', slug: 'hp',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -99,7 +99,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -112,7 +112,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -125,7 +125,7 @@ const ax: ItemSkill[][] = [
ja: '背水', ja: '背水',
}, },
id: 11, id: 11,
granblue_id: '1601', granblueId: '1601',
slug: 'enmity', slug: 'enmity',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -139,7 +139,7 @@ const ax: ItemSkill[][] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '1588', granblueId: '1588',
slug: 'hp', slug: 'hp',
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
@ -152,7 +152,7 @@ const ax: ItemSkill[][] = [
ja: '防御', ja: '防御',
}, },
id: 1, id: 1,
granblue_id: '1590', granblueId: '1590',
slug: 'def', slug: 'def',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -165,7 +165,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -178,7 +178,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -191,7 +191,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -205,7 +205,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
@ -218,7 +218,7 @@ const ax: ItemSkill[][] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 0, id: 0,
granblue_id: '1589', granblueId: '1589',
slug: 'atk', slug: 'atk',
minValue: 1, minValue: 1,
maxValue: 1.5, maxValue: 1.5,
@ -231,7 +231,7 @@ const ax: ItemSkill[][] = [
ja: '全属性攻撃力', ja: '全属性攻撃力',
}, },
id: 13, id: 13,
granblue_id: '1594', granblueId: '1594',
slug: 'ele-atk', slug: 'ele-atk',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -244,7 +244,7 @@ const ax: ItemSkill[][] = [
ja: '奥義上限', ja: '奥義上限',
}, },
id: 8, id: 8,
granblue_id: '1599', granblueId: '1599',
slug: 'ca-cap', slug: 'ca-cap',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -257,7 +257,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -271,7 +271,7 @@ const ax: ItemSkill[][] = [
ja: '連撃率', ja: '連撃率',
}, },
id: 4, id: 4,
granblue_id: '1592', granblueId: '1592',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
@ -284,7 +284,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
@ -297,7 +297,7 @@ const ax: ItemSkill[][] = [
ja: '全属性攻撃力', ja: '全属性攻撃力',
}, },
id: 13, id: 13,
granblue_id: '1594', granblueId: '1594',
slug: 'ele-atk', slug: 'ele-atk',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -310,7 +310,7 @@ const ax: ItemSkill[][] = [
ja: 'DA確率', ja: 'DA確率',
}, },
id: 5, id: 5,
granblue_id: '1596', granblueId: '1596',
slug: 'da', slug: 'da',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -323,7 +323,7 @@ const ax: ItemSkill[][] = [
ja: 'TA確率', ja: 'TA確率',
}, },
id: 6, id: 6,
granblue_id: '1597', granblueId: '1597',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -340,7 +340,7 @@ const ax: ItemSkill[][] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 0, id: 0,
granblue_id: '1589', granblueId: '1589',
slug: 'atk', slug: 'atk',
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
@ -353,7 +353,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
@ -366,7 +366,7 @@ const ax: ItemSkill[][] = [
ja: '連撃確率', ja: '連撃確率',
}, },
id: 4, id: 4,
granblue_id: '1592', granblueId: '1592',
slug: 'ta', slug: 'ta',
minValue: 1.5, minValue: 1.5,
maxValue: 4, maxValue: 4,
@ -379,7 +379,7 @@ const ax: ItemSkill[][] = [
ja: '通常ダメ上限', ja: '通常ダメ上限',
}, },
id: 14, id: 14,
granblue_id: '1722', granblueId: '1722',
slug: 'na-dmg', slug: 'na-dmg',
minValue: 0.5, minValue: 0.5,
maxValue: 1.5, maxValue: 1.5,
@ -392,7 +392,7 @@ const ax: ItemSkill[][] = [
ja: 'アビ与ダメ上昇', ja: 'アビ与ダメ上昇',
}, },
id: 15, id: 15,
granblue_id: '1719', granblueId: '1719',
slug: 'skill-supp', slug: 'skill-supp',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -406,7 +406,7 @@ const ax: ItemSkill[][] = [
ja: '防御', ja: '防御',
}, },
id: 1, id: 1,
granblue_id: '1590', granblueId: '1590',
slug: 'def', slug: 'def',
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
@ -419,7 +419,7 @@ const ax: ItemSkill[][] = [
ja: '属性ダメ軽減', ja: '属性ダメ軽減',
}, },
id: 17, id: 17,
granblue_id: '1721', granblueId: '1721',
slug: 'ele-def', slug: 'ele-def',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -432,7 +432,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -445,7 +445,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -458,7 +458,7 @@ const ax: ItemSkill[][] = [
ja: '背水', ja: '背水',
}, },
id: 11, id: 11,
granblue_id: '1601', granblueId: '1601',
slug: 'enmity', slug: 'enmity',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -472,7 +472,7 @@ const ax: ItemSkill[][] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '1588', granblueId: '1588',
slug: 'hp', slug: 'hp',
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
@ -485,7 +485,7 @@ const ax: ItemSkill[][] = [
ja: '属性ダメ軽減', ja: '属性ダメ軽減',
}, },
id: 17, id: 17,
granblue_id: '1721', granblueId: '1721',
slug: 'ele-def', slug: 'ele-def',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -498,7 +498,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -511,7 +511,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -524,7 +524,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -538,7 +538,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
@ -551,7 +551,7 @@ const ax: ItemSkill[][] = [
ja: '連撃率', ja: '連撃率',
}, },
id: 4, id: 4,
granblue_id: '1592', granblueId: '1592',
slug: 'ta', slug: 'ta',
minValue: 1.5, minValue: 1.5,
maxValue: 4, maxValue: 4,
@ -564,7 +564,7 @@ const ax: ItemSkill[][] = [
ja: 'アビ与ダメ上昇', ja: 'アビ与ダメ上昇',
}, },
id: 15, id: 15,
granblue_id: '1719', granblueId: '1719',
slug: 'skill-supp', slug: 'skill-supp',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -576,7 +576,7 @@ const ax: ItemSkill[][] = [
ja: '奥義与ダメ上昇', ja: '奥義与ダメ上昇',
}, },
id: 16, id: 16,
granblue_id: '1720', granblueId: '1720',
slug: 'ca-supp', slug: 'ca-supp',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -588,7 +588,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -602,7 +602,7 @@ const ax: ItemSkill[][] = [
ja: '連撃率', ja: '連撃率',
}, },
id: 4, id: 4,
granblue_id: '1592', granblueId: '1592',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
@ -615,7 +615,7 @@ const ax: ItemSkill[][] = [
ja: '奥義与ダメ上昇', ja: '奥義与ダメ上昇',
}, },
id: 16, id: 16,
granblue_id: '1720', granblueId: '1720',
slug: 'ca-supp', slug: 'ca-supp',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -627,7 +627,7 @@ const ax: ItemSkill[][] = [
ja: '通常ダメ上限', ja: '通常ダメ上限',
}, },
id: 14, id: 14,
granblue_id: '1722', granblueId: '1722',
slug: 'na-cap', slug: 'na-cap',
minValue: 0.5, minValue: 0.5,
maxValue: 1.5, maxValue: 1.5,
@ -640,7 +640,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -652,7 +652,7 @@ const ax: ItemSkill[][] = [
ja: '背水', ja: '背水',
}, },
id: 11, id: 11,
granblue_id: '1601', granblueId: '1601',
slug: 'enmity', slug: 'enmity',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -668,7 +668,7 @@ const ax: ItemSkill[][] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 0, id: 0,
granblue_id: '1589', granblueId: '1589',
slug: 'atk', slug: 'atk',
minValue: 1, minValue: 1,
maxValue: 3.5, maxValue: 3.5,
@ -681,7 +681,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
@ -694,7 +694,7 @@ const ax: ItemSkill[][] = [
ja: 'DA確率', ja: 'DA確率',
}, },
id: 5, id: 5,
granblue_id: '1596', granblueId: '1596',
slug: 'da', slug: 'da',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -707,7 +707,7 @@ const ax: ItemSkill[][] = [
ja: 'TA確率', ja: 'TA確率',
}, },
id: 6, id: 6,
granblue_id: '1597', granblueId: '1597',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -720,7 +720,7 @@ const ax: ItemSkill[][] = [
ja: 'アビ上限', ja: 'アビ上限',
}, },
id: 7, id: 7,
granblue_id: '1588', granblueId: '1588',
slug: 'skill-cap', slug: 'skill-cap',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -735,7 +735,7 @@ const ax: ItemSkill[][] = [
ja: '防御', ja: '防御',
}, },
id: 1, id: 1,
granblue_id: '1590', granblueId: '1590',
slug: 'def', slug: 'def',
minValue: 1, minValue: 1,
maxValue: 8, maxValue: 8,
@ -748,7 +748,7 @@ const ax: ItemSkill[][] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '1588', granblueId: '1588',
slug: 'hp', slug: 'hp',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -761,7 +761,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -774,7 +774,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -787,7 +787,7 @@ const ax: ItemSkill[][] = [
ja: '背水', ja: '背水',
}, },
id: 11, id: 11,
granblue_id: '1601', granblueId: '1601',
slug: 'enmity', slug: 'enmity',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -801,7 +801,7 @@ const ax: ItemSkill[][] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '1588', granblueId: '1588',
slug: 'hp', slug: 'hp',
minValue: 1, minValue: 1,
maxValue: 11, maxValue: 11,
@ -814,7 +814,7 @@ const ax: ItemSkill[][] = [
ja: '防御', ja: '防御',
}, },
id: 1, id: 1,
granblue_id: '1590', granblueId: '1590',
slug: 'def', slug: 'def',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -827,7 +827,7 @@ const ax: ItemSkill[][] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 9, id: 9,
granblue_id: '1593', granblueId: '1593',
slug: 'debuff', slug: 'debuff',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -840,7 +840,7 @@ const ax: ItemSkill[][] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 10, id: 10,
granblue_id: '1595', granblueId: '1595',
slug: 'healing', slug: 'healing',
minValue: 2, minValue: 2,
maxValue: 5, maxValue: 5,
@ -853,7 +853,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -867,7 +867,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 8.5, maxValue: 8.5,
@ -880,7 +880,7 @@ const ax: ItemSkill[][] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 0, id: 0,
granblue_id: '1589', granblueId: '1589',
slug: 'atk', slug: 'atk',
minValue: 1, minValue: 1,
maxValue: 1.5, maxValue: 1.5,
@ -893,7 +893,7 @@ const ax: ItemSkill[][] = [
ja: '全属性攻撃力', ja: '全属性攻撃力',
}, },
id: 13, id: 13,
granblue_id: '1594', granblueId: '1594',
slug: 'ele-atk', slug: 'ele-atk',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -906,7 +906,7 @@ const ax: ItemSkill[][] = [
ja: '奥義上限', ja: '奥義上限',
}, },
id: 8, id: 8,
granblue_id: '1599', granblueId: '1599',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -919,7 +919,7 @@ const ax: ItemSkill[][] = [
ja: '渾身', ja: '渾身',
}, },
id: 12, id: 12,
granblue_id: '1600', granblueId: '1600',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 3, maxValue: 3,
@ -933,7 +933,7 @@ const ax: ItemSkill[][] = [
ja: '連撃率', ja: '連撃率',
}, },
id: 4, id: 4,
granblue_id: '1592', granblueId: '1592',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 4, maxValue: 4,
@ -946,7 +946,7 @@ const ax: ItemSkill[][] = [
ja: '奥義ダメ', ja: '奥義ダメ',
}, },
id: 3, id: 3,
granblue_id: '1591', granblueId: '1591',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 2, minValue: 2,
maxValue: 4, maxValue: 4,
@ -959,7 +959,7 @@ const ax: ItemSkill[][] = [
ja: '全属性攻撃力', ja: '全属性攻撃力',
}, },
id: 13, id: 13,
granblue_id: '1594', granblueId: '1594',
slug: 'ele-atk', slug: 'ele-atk',
minValue: 1, minValue: 1,
maxValue: 5, maxValue: 5,
@ -972,7 +972,7 @@ const ax: ItemSkill[][] = [
ja: 'DA確率', ja: 'DA確率',
}, },
id: 5, id: 5,
granblue_id: '1596', granblueId: '1596',
slug: 'da', slug: 'da',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -985,7 +985,7 @@ const ax: ItemSkill[][] = [
ja: 'TA確率', ja: 'TA確率',
}, },
id: 6, id: 6,
granblue_id: '1597', granblueId: '1597',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 2, maxValue: 2,
@ -1000,7 +1000,7 @@ const ax: ItemSkill[][] = [
ja: 'EXP UP', ja: 'EXP UP',
}, },
id: 18, id: 18,
granblue_id: '1837', granblueId: '1837',
slug: 'exp', slug: 'exp',
minValue: 5, minValue: 5,
maxValue: 10, maxValue: 10,
@ -1013,7 +1013,7 @@ const ax: ItemSkill[][] = [
ja: '獲得ルピ', ja: '獲得ルピ',
}, },
id: 19, id: 19,
granblue_id: '1838', granblueId: '1838',
slug: 'rupie', slug: 'rupie',
minValue: 10, minValue: 10,
maxValue: 20, maxValue: 20,

View file

@ -5,7 +5,7 @@ const overMasteryPrimary: ItemSkill[] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 1, id: 1,
granblue_id: '', granblueId: '',
slug: 'atk', slug: 'atk',
minValue: 300, minValue: 300,
maxValue: 3000, maxValue: 3000,
@ -19,7 +19,7 @@ const overMasteryPrimary: ItemSkill[] = [
ja: 'HP', ja: 'HP',
}, },
id: 2, id: 2,
granblue_id: '', granblueId: '',
slug: 'hp', slug: 'hp',
minValue: 150, minValue: 150,
maxValue: 1500, maxValue: 1500,
@ -36,7 +36,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: '弱体成功率', ja: '弱体成功率',
}, },
id: 3, id: 3,
granblue_id: '', granblueId: '',
slug: 'debuff-success', slug: 'debuff-success',
minValue: 6, minValue: 6,
maxValue: 15, maxValue: 15,
@ -50,7 +50,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: 'アビダメ上限', ja: 'アビダメ上限',
}, },
id: 4, id: 4,
granblue_id: '', granblueId: '',
slug: 'skill-cap', slug: 'skill-cap',
minValue: 6, minValue: 6,
maxValue: 15, maxValue: 15,
@ -64,7 +64,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: '奥義ダメージ', ja: '奥義ダメージ',
}, },
id: 5, id: 5,
granblue_id: '', granblueId: '',
slug: 'ca-dmg', slug: 'ca-dmg',
minValue: 10, minValue: 10,
maxValue: 30, maxValue: 30,
@ -78,7 +78,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: '奥義ダメージ上限', ja: '奥義ダメージ上限',
}, },
id: 6, id: 6,
granblue_id: '', granblueId: '',
slug: 'ca-cap', slug: 'ca-cap',
minValue: 6, minValue: 6,
maxValue: 15, maxValue: 15,
@ -92,7 +92,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: '渾身', ja: '渾身',
}, },
id: 7, id: 7,
granblue_id: '', granblueId: '',
slug: 'stamina', slug: 'stamina',
minValue: 1, minValue: 1,
maxValue: 10, maxValue: 10,
@ -106,7 +106,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: '背水', ja: '背水',
}, },
id: 8, id: 8,
granblue_id: '', granblueId: '',
slug: 'enmity', slug: 'enmity',
minValue: 1, minValue: 1,
maxValue: 10, maxValue: 10,
@ -120,7 +120,7 @@ const overMasterySecondary: ItemSkill[] = [
ja: 'クリティカル確率', ja: 'クリティカル確率',
}, },
id: 9, id: 9,
granblue_id: '', granblueId: '',
slug: 'crit', slug: 'crit',
minValue: 10, minValue: 10,
maxValue: 30, maxValue: 30,
@ -137,7 +137,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: 'ダブルアタック確率', ja: 'ダブルアタック確率',
}, },
id: 10, id: 10,
granblue_id: '', granblueId: '',
slug: 'da', slug: 'da',
minValue: 6, minValue: 6,
maxValue: 15, maxValue: 15,
@ -151,7 +151,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: 'トリプルアタック確率', ja: 'トリプルアタック確率',
}, },
id: 11, id: 11,
granblue_id: '', granblueId: '',
slug: 'ta', slug: 'ta',
minValue: 1, minValue: 1,
maxValue: 10, maxValue: 10,
@ -165,7 +165,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: '防御', ja: '防御',
}, },
id: 12, id: 12,
granblue_id: '', granblueId: '',
slug: 'def', slug: 'def',
minValue: 6, minValue: 6,
maxValue: 20, maxValue: 20,
@ -179,7 +179,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: '回復性能', ja: '回復性能',
}, },
id: 13, id: 13,
granblue_id: '', granblueId: '',
slug: 'heal', slug: 'heal',
minValue: 3, minValue: 3,
maxValue: 30, maxValue: 30,
@ -193,7 +193,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: '弱体耐性', ja: '弱体耐性',
}, },
id: 14, id: 14,
granblue_id: '', granblueId: '',
slug: 'debuff-resist', slug: 'debuff-resist',
minValue: 6, minValue: 6,
maxValue: 15, maxValue: 15,
@ -207,7 +207,7 @@ const overMasteryTertiary: ItemSkill[] = [
ja: '回避', ja: '回避',
}, },
id: 15, id: 15,
granblue_id: '', granblueId: '',
slug: 'dodge', slug: 'dodge',
minValue: 1, minValue: 1,
maxValue: 10, maxValue: 10,
@ -230,7 +230,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: 'ダブルアタック確率', ja: 'ダブルアタック確率',
}, },
id: 1, id: 1,
granblue_id: '', granblueId: '',
slug: 'da', slug: 'da',
minValue: 10, minValue: 10,
maxValue: 17, maxValue: 17,
@ -243,7 +243,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: 'トリプルアタック確率', ja: 'トリプルアタック確率',
}, },
id: 2, id: 2,
granblue_id: '', granblueId: '',
slug: 'ta', slug: 'ta',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -256,7 +256,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: '{属性}攻撃', ja: '{属性}攻撃',
}, },
id: 3, id: 3,
granblue_id: '', granblueId: '',
slug: 'element-atk', slug: 'element-atk',
minValue: 15, minValue: 15,
maxValue: 22, maxValue: 22,
@ -269,7 +269,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: '{属性}軽減', ja: '{属性}軽減',
}, },
id: 4, id: 4,
granblue_id: '', granblueId: '',
slug: 'element-resist', slug: 'element-resist',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -282,7 +282,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: '渾身', ja: '渾身',
}, },
id: 5, id: 5,
granblue_id: '', granblueId: '',
slug: 'stamina', slug: 'stamina',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -295,7 +295,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: '背水', ja: '背水',
}, },
id: 6, id: 6,
granblue_id: '', granblueId: '',
slug: 'enmity', slug: 'enmity',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -308,7 +308,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: '与ダメ上昇', ja: '与ダメ上昇',
}, },
id: 7, id: 7,
granblue_id: '', granblueId: '',
slug: 'supplemental', slug: 'supplemental',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -321,7 +321,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: 'クリティカル', ja: 'クリティカル',
}, },
id: 8, id: 8,
granblue_id: '', granblueId: '',
slug: 'crit', slug: 'crit',
minValue: 18, minValue: 18,
maxValue: 35, maxValue: 35,
@ -334,7 +334,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: 'カウンター(回避)', ja: 'カウンター(回避)',
}, },
id: 9, id: 9,
granblue_id: '', granblueId: '',
slug: 'counter-dodge', slug: 'counter-dodge',
minValue: 5, minValue: 5,
maxValue: 12, maxValue: 12,
@ -347,7 +347,7 @@ export const aetherialMastery: ItemSkill[] = [
ja: 'カウンター(被ダメ)', ja: 'カウンター(被ダメ)',
}, },
id: 10, id: 10,
granblue_id: '', granblueId: '',
slug: 'counter-dmg', slug: 'counter-dmg',
minValue: 10, minValue: 10,
maxValue: 17, maxValue: 17,
@ -363,7 +363,7 @@ export const permanentMastery: ItemSkill[] = [
ja: 'LB強化回数上限', ja: 'LB強化回数上限',
}, },
id: 1, id: 1,
granblue_id: '', granblueId: '',
slug: 'star-cap', slug: 'star-cap',
minValue: 10, minValue: 10,
maxValue: 10, maxValue: 10,
@ -376,7 +376,7 @@ export const permanentMastery: ItemSkill[] = [
ja: '攻撃', ja: '攻撃',
}, },
id: 2, id: 2,
granblue_id: '', granblueId: '',
slug: 'atk', slug: 'atk',
minValue: 10, minValue: 10,
maxValue: 10, maxValue: 10,
@ -389,7 +389,7 @@ export const permanentMastery: ItemSkill[] = [
ja: 'HP', ja: 'HP',
}, },
id: 3, id: 3,
granblue_id: '', granblueId: '',
slug: 'hp', slug: 'hp',
minValue: 10, minValue: 10,
maxValue: 10, maxValue: 10,
@ -402,7 +402,7 @@ export const permanentMastery: ItemSkill[] = [
ja: 'ダメージ上限', ja: 'ダメージ上限',
}, },
id: 4, id: 4,
granblue_id: '', granblueId: '',
slug: 'dmg-cap', slug: 'dmg-cap',
minValue: 5, minValue: 5,
maxValue: 5, maxValue: 5,

14
package-lock.json generated
View file

@ -23,6 +23,7 @@
"@tiptap/extension-highlight": "^2.0.3", "@tiptap/extension-highlight": "^2.0.3",
"@tiptap/extension-link": "^2.0.3", "@tiptap/extension-link": "^2.0.3",
"@tiptap/extension-mention": "^2.0.3", "@tiptap/extension-mention": "^2.0.3",
"@tiptap/extension-placeholder": "^2.0.3",
"@tiptap/extension-typography": "^2.0.3", "@tiptap/extension-typography": "^2.0.3",
"@tiptap/extension-youtube": "^2.0.3", "@tiptap/extension-youtube": "^2.0.3",
"@tiptap/pm": "^2.0.3", "@tiptap/pm": "^2.0.3",
@ -7348,6 +7349,19 @@
"@tiptap/core": "^2.0.0" "@tiptap/core": "^2.0.0"
} }
}, },
"node_modules/@tiptap/extension-placeholder": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.0.3.tgz",
"integrity": "sha512-Z42jo0termRAf0S0L8oxrts94IWX5waU4isS2CUw8xCUigYyCFslkhQXkWATO1qRbjNFLKN2C9qvCgGf4UeBrw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.0.0",
"@tiptap/pm": "^2.0.0"
}
},
"node_modules/@tiptap/extension-strike": { "node_modules/@tiptap/extension-strike": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.3.tgz",

View file

@ -30,6 +30,7 @@
"@tiptap/extension-highlight": "^2.0.3", "@tiptap/extension-highlight": "^2.0.3",
"@tiptap/extension-link": "^2.0.3", "@tiptap/extension-link": "^2.0.3",
"@tiptap/extension-mention": "^2.0.3", "@tiptap/extension-mention": "^2.0.3",
"@tiptap/extension-placeholder": "^2.0.3",
"@tiptap/extension-typography": "^2.0.3", "@tiptap/extension-typography": "^2.0.3",
"@tiptap/extension-youtube": "^2.0.3", "@tiptap/extension-youtube": "^2.0.3",
"@tiptap/pm": "^2.0.3", "@tiptap/pm": "^2.0.3",

View file

@ -22,6 +22,8 @@ import FilterBar from '~components/filters/FilterBar'
import ProfileHead from '~components/head/ProfileHead' import ProfileHead from '~components/head/ProfileHead'
import UserInfo from '~components/filters/UserInfo' import UserInfo from '~components/filters/UserInfo'
import * as RaidGroupTransformer from '~transformers/RaidGroupTransformer'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
import type { import type {
@ -175,7 +177,11 @@ const ProfileRoute: React.FC<Props> = ({
function replaceResults(count: number, list: Party[]) { function replaceResults(count: number, list: Party[]) {
if (count > 0) { if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))) setParties(
list.sort((a, b) =>
a.timestamps.createdAt > b.timestamps.createdAt ? -1 : 1
)
)
} else { } else {
setParties([]) setParties([])
} }
@ -252,13 +258,13 @@ const ProfileRoute: React.FC<Props> = ({
id={party.id} id={party.id}
shortcode={party.shortcode} shortcode={party.shortcode}
name={party.name} name={party.name}
createdAt={new Date(party.created_at)} createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid} raid={party.raid}
grid={party.weapons} weapons={party.grid.weapons}
user={party.user} user={party.user}
favorited={party.favorited} favorited={party.social.favorited}
fullAuto={party.full_auto} fullAuto={party.details.fullAuto}
autoGuard={party.auto_guard} autoGuard={party.details.autoGuard}
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}
/> />
@ -331,9 +337,11 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
try { try {
// Fetch and organize raids // Fetch and organize raids
let raidGroups: RaidGroup[] = await api const raidGroups: RaidGroup[] = await api
.raidGroups() .raidGroups()
.then((response) => response.data) .then((response) =>
response.data.map((group: any) => RaidGroupTransformer.toObject(group))
)
// Create filter object // Create filter object
const filters: FilterObject = extractFilters(query, raidGroups) const filters: FilterObject = extractFilters(query, raidGroups)

View file

@ -1,4 +1,5 @@
import { appWithTranslation } from 'next-i18next' import { appWithTranslation } from 'next-i18next'
import Head from 'next/head'
import Link from 'next/link' import Link from 'next/link'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { get } from 'local-storage' import { get } from 'local-storage'
@ -131,20 +132,28 @@ function MyApp({ Component, pageProps }: AppProps) {
} }
return ( return (
<ThemeProvider> <>
<ToastProvider swipeDirection="right"> <Head>
<TooltipProvider> <meta
<Layout> name="viewport"
{!appState.version ? ( content="viewport-fit=cover, width=device-width, initial-scale=1.0"
serverUnavailable() />
) : ( </Head>
<Component {...pageProps} /> <ThemeProvider>
)} <ToastProvider swipeDirection="right">
</Layout> <TooltipProvider>
<Viewport className="ToastViewport" /> <Layout>
</TooltipProvider> {!appState.version ? (
</ToastProvider> serverUnavailable()
</ThemeProvider> ) : (
<Component {...pageProps} />
)}
</Layout>
<Viewport className="ToastViewport" />
</TooltipProvider>
</ToastProvider>
</ThemeProvider>
</>
) )
} }

View file

@ -11,7 +11,13 @@ import elementEmoji from '~utils/elementEmoji'
import fetchLatestVersion from '~utils/fetchLatestVersion' import fetchLatestVersion from '~utils/fetchLatestVersion'
import { setHeaders } from '~utils/userToken' import { setHeaders } from '~utils/userToken'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import { groupWeaponKeys } from '~utils/groupWeaponKeys' import { GroupedWeaponKeys, groupWeaponKeys } from '~utils/groupWeaponKeys'
import * as JobTransformer from '~transformers/JobTransformer'
import * as JobSkillTransformer from '~transformers/JobSkillTransformer'
import * as PartyTransformer from '~transformers/PartyTransformer'
import * as RaidGroupTransformer from '~transformers/RaidGroupTransformer'
import * as WeaponKeyTransformer from '~transformers/WeaponKeyTransformer'
import { GridType } from '~utils/enums' import { GridType } from '~utils/enums'
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
@ -90,8 +96,8 @@ const PartyRoute: React.FC<Props> = ({
break break
} }
if (router.asPath !== '/new' && router.asPath !== '/') // if (router.asPath !== '/new' && router.asPath !== '/')
router.replace(path, undefined, { shallow: true }) // router.replace(path, undefined, { shallow: true })
} }
// Set the initial data from props // Set the initial data from props
@ -126,7 +132,7 @@ const PartyRoute: React.FC<Props> = ({
<React.Fragment key={router.asPath}> <React.Fragment key={router.asPath}>
{pageHead()} {pageHead()}
<Party <Party
team={context.party} party={context.party}
selectedTab={selectedTab} selectedTab={selectedTab}
raidGroups={context.raidGroups} raidGroups={context.raidGroups}
handleTabChanged={handleTabChange} handleTabChanged={handleTabChange}
@ -156,34 +162,43 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
try { try {
// Fetch and organize raids // Fetch and organize raids
let raidGroups: RaidGroup[] = await api const raidGroups: RaidGroup[] = await api
.raidGroups() .raidGroups()
.then((response) => response.data) .then((response) => response.data.map((group: any) => RaidGroupTransformer.toObject(group)))
// Fetch jobs and job skills // Fetch jobs and job skills
let jobs = await api.endpoints.jobs const jobs: Job[] = await api.endpoints.jobs
.getAll() .getAll()
.then((response) => response.data) .then((response) =>
response.data.map((job: any) => JobTransformer.toObject(job))
)
let jobSkills = await api.allJobSkills() const jobSkills: JobSkill[] = await api
.then((response) => response.data) .allJobSkills()
.then((response) =>
response.data.map((skill: any) => JobSkillTransformer.toObject(skill))
)
// Fetch and organize weapon keys // Fetch and organize weapon keys
let weaponKeys = await api.endpoints.weapon_keys const weaponKeys: GroupedWeaponKeys = await api.endpoints.weapon_keys
.getAll() .getAll()
.then((response) => groupWeaponKeys(response.data)) .then((response) =>
response.data.map((key: any) => WeaponKeyTransformer.toObject(key))
)
.then((keys) => groupWeaponKeys(keys))
// Fetch the party // Fetch the party
let party: Party | undefined = undefined if (!query.party) throw new Error('No party code')
if (query.party) {
let response = await api.endpoints.parties.getOne({
id: query.party,
})
party = response.data.party
} else {
console.error('No party code')
}
const party: Party | undefined = await api.endpoints.parties.getOne({
id: query.party,
}).then((response) =>
PartyTransformer.toObject(response.data.party)
)
console.log(party)
// Consolidate data into context object // Consolidate data into context object
const context: PageContextObj = { const context: PageContextObj = {
party: party, party: party,
@ -206,6 +221,8 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
}, },
} }
} catch (error) { } catch (error) {
console.error(error)
// Extract the underlying Axios error // Extract the underlying Axios error
const axiosError = error as AxiosError const axiosError = error as AxiosError
const response = axiosError.response const response = axiosError.response

View file

@ -238,7 +238,7 @@ const SavedRoute: React.FC<Props> = ({
const index = parties.findIndex((p) => p.id === teamId) const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index] const party = parties[index]
party.favorited = true party.social.favorited = true
let clonedParties = clonedeep(parties) let clonedParties = clonedeep(parties)
clonedParties[index] = party clonedParties[index] = party
@ -254,7 +254,7 @@ const SavedRoute: React.FC<Props> = ({
const index = parties.findIndex((p) => p.id === teamId) const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index] const party = parties[index]
party.favorited = false party.social.favorited = false
let clonedParties = clonedeep(parties) let clonedParties = clonedeep(parties)
clonedParties.splice(index, 1) clonedParties.splice(index, 1)
@ -291,13 +291,13 @@ const SavedRoute: React.FC<Props> = ({
id={party.id} id={party.id}
shortcode={party.shortcode} shortcode={party.shortcode}
name={party.name} name={party.name}
createdAt={new Date(party.created_at)} createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid} raid={party.raid}
grid={party.weapons} weapons={party.grid.weapons}
user={party.user} user={party.user}
favorited={party.favorited} favorited={party.social.favorited}
fullAuto={party.full_auto} fullAuto={party.details.fullAuto}
autoGuard={party.auto_guard} autoGuard={party.details.autoGuard}
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}

View file

@ -189,7 +189,11 @@ const TeamsRoute: React.FC<Props> = ({
function replaceResults(count: number, list: Party[]) { function replaceResults(count: number, list: Party[]) {
if (count > 0) { if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))) setParties(
list.sort((a, b) =>
a.timestamps.createdAt > b.timestamps.createdAt ? -1 : 1
)
)
} else { } else {
setParties([]) setParties([])
} }
@ -249,7 +253,7 @@ const TeamsRoute: React.FC<Props> = ({
const index = parties.findIndex((p) => p.id === teamId) const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index] const party = parties[index]
party.favorited = true party.social.favorited = true
let clonedParties = clonedeep(parties) let clonedParties = clonedeep(parties)
clonedParties[index] = party clonedParties[index] = party
@ -265,7 +269,7 @@ const TeamsRoute: React.FC<Props> = ({
const index = parties.findIndex((p) => p.id === teamId) const index = parties.findIndex((p) => p.id === teamId)
const party = parties[index] const party = parties[index]
party.favorited = false party.social.favorited = false
let clonedParties = clonedeep(parties) let clonedParties = clonedeep(parties)
clonedParties[index] = party clonedParties[index] = party
@ -302,13 +306,13 @@ const TeamsRoute: React.FC<Props> = ({
id={party.id} id={party.id}
shortcode={party.shortcode} shortcode={party.shortcode}
name={party.name} name={party.name}
createdAt={new Date(party.created_at)} createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid} raid={party.raid}
grid={party.weapons} weapons={party.grid.weapons}
user={party.user} user={party.user}
favorited={party.favorited} favorited={party.social.favorited}
fullAuto={party.full_auto} fullAuto={party.details.fullAuto}
autoGuard={party.auto_guard} autoGuard={party.details.autoGuard}
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}

View file

@ -557,6 +557,19 @@
"tokens": { "tokens": {
"remix": "Remixed" "remix": "Remixed"
}, },
"toolbar": {
"tooltips": {
"bold": "Bold",
"italic": "Italic",
"strike": "Strikethrough",
"highlight": "Highlight",
"link": "Add a link",
"youtube": "Add a Youtube video",
"heading": "Heading {{level}}",
"bulletList": "Bullet list",
"orderedList": "Numbered list"
}
},
"tooltips": { "tooltips": {
"copy_url": "Copy the URL to this team", "copy_url": "Copy the URL to this team",
"new": "Create a new team", "new": "Create a new team",

View file

@ -555,6 +555,19 @@
"tokens": { "tokens": {
"remix": "リミックスされた" "remix": "リミックスされた"
}, },
"toolbar": {
"tooltips": {
"bold": "太字",
"italic": "斜体",
"strike": "取り消し線",
"highlight": "ハイライト",
"link": "リンクを挿入",
"youtube": "Youtube動画を埋め込む",
"heading": "見出し {{level}}",
"bulletList": "箇条書き",
"orderedList": "番号リスト"
}
},
"tooltips": { "tooltips": {
"copy_url": "この編成のURLをコピーする", "copy_url": "この編成のURLをコピーする",
"new": "新しい編成を作成する", "new": "新しい編成を作成する",

View file

@ -159,6 +159,14 @@
--grid-border-color: #{$grid--border--color--light}; --grid-border-color: #{$grid--border--color--light};
// Light - Element theming // Light - Element theming
--null-bg: #{$null--bg--light};
--null-bg-hover: #{$null--bg--hover--light};
--null-text: #{$null--text--light};
--null-raid-text: #{$null--text--raid--light};
--null-text-hover: #{$null--text--hover--light};
--null-shadow: #{$null--shadow--light};
--null-shadow-hover: #{$null--shadow--light--hover};
--wind-bg: #{$wind--bg--light}; --wind-bg: #{$wind--bg--light};
--wind-bg-hover: #{$wind--bg--hover--light}; --wind-bg-hover: #{$wind--bg--hover--light};
--wind-text: #{$wind--text--light}; --wind-text: #{$wind--text--light};
@ -373,6 +381,14 @@
--grid-border-color: #{$grid--border--color--dark}; --grid-border-color: #{$grid--border--color--dark};
// Dark - Element theming // Dark - Element theming
--null-bg: #{$null--bg--dark};
--null-bg-hover: #{$null--bg--hover--dark};
--null-text: #{$null--text--dark};
--null-raid-text: #{$null--text--raid--dark};
--null-text-hover: #{$null--text--hover--dark};
--null-shadow: #{$null--shadow--dark};
--null-shadow-hover: #{$null--shadow--dark--hover};
--wind-bg: #{$wind--bg--dark}; --wind-bg: #{$wind--bg--dark};
--wind-bg-hover: #{$wind--bg--hover--dark}; --wind-bg-hover: #{$wind--bg--hover--dark};
--wind-text: #{$wind--text--dark}; --wind-text: #{$wind--text--dark};

View file

@ -464,6 +464,28 @@ $wind--shadow--dark: fade-out($wind-text-20, 0.3);
$wind--shadow--light--hover: fade-out($wind-text-00, 0.3); $wind--shadow--light--hover: fade-out($wind-text-00, 0.3);
$wind--shadow--dark--hover: fade-out($wind-text-00, 0.3); $wind--shadow--dark--hover: fade-out($wind-text-00, 0.3);
// Color Definitions: Element / Null
$null--bg--light: $grey-75;
$null--bg--dark: $grey-40;
$null--bg--hover--light: $grey-70;
$null--bg--hover--dark: $grey-30;
$null--text--light: $grey-40;
$null--text--dark: $grey-90;
$null--text--raid--light: $grey-40;
$null--text--raid--dark: $grey-90;
$null--text--hover--light: $grey-20;
$null--text--hover--dark: $grey-90;
$null--shadow--light: fade-out($grey-60, 0.3);
$null--shadow--dark: fade-out($grey-25, 0.3);
$null--shadow--light--hover: fade-out($grey-50, 0.3);
$null--shadow--dark--hover: fade-out($grey-10, 0.3);
// Color Definitions: Element / Fire // Color Definitions: Element / Fire
$fire--bg--light: $fire-bg-10; $fire--bg--light: $fire-bg-10;
$fire--bg--dark: $fire-bg-10; $fire--bg--dark: $fire-bg-10;

View file

@ -0,0 +1,13 @@
// Transforms API response to Awakening object
export function toObject(data: any): Awakening {
return {
id: data.id,
name: {
en: data.name.en,
ja: data.name.ja,
},
slug: data.slug,
object_type: data.object_type,
order: data.order,
}
}

View file

@ -0,0 +1,41 @@
import * as Awakening from './AwakeningTransformer'
import * as Element from './ElementTransformer'
// Transforms API response to Character object
export function toObject(data: any): Character {
return {
type: 'character',
id: data.id,
granblueId: data.granblue_id,
characterId: data.character_id,
name: {
en: data.name.en,
ja: data.name.ja,
},
element: Element.toObject(data.element),
rarity: data.rarity,
proficiency: data.proficiency,
gender: data.gender,
race: data.race,
hp: {
min: data.hp.min_hp,
max: data.hp.max_hp,
flb: data.hp.max_hp_flb ? data.hp.max_hp_flb : null,
ulb: data.hp.max_hp_ulb ? data.hp.max_hp_ulb : null,
},
atk: {
min: data.atk.min_atk,
max: data.atk.max_atk,
flb: data.atk.max_atk_flb ? data.atk.max_atk_flb : null,
ulb: data.atk.max_atk_ulb ? data.atk.max_atk_ulb : null,
},
uncap: {
flb: data.uncap.flb,
ulb: data.uncap.ulb,
},
awakenings: data.awakenings.map((awakening: any) =>
Awakening.toObject(awakening)
),
special: data.special,
}
}

View file

@ -0,0 +1,9 @@
import { elements } from '../utils/elements'
export function toObject(value: number) {
return elements.find((element) => element.id === value) || elements[0]
}
export function toParam(value: string) {
return elements.find((element) => element.name.en === value)
}

View file

@ -0,0 +1,53 @@
import * as Awakening from './AwakeningTransformer'
import * as Character from './CharacterTransformer'
// Transforms API response to GridCharacter object
export function toObject(data: any): GridCharacter {
return {
id: data.id,
object: Character.toObject(data.object),
position: data.position,
uncapLevel: data.uncap_level,
transcendenceStep: data.transcendence_step ? data.transcendence_step : null,
mastery: {
overMastery: data.over_mastery
? data.over_mastery
: [
{
modifier: 1,
strength: 0,
},
{
modifier: 2,
strength: 0,
},
null,
null,
],
aetherialMastery: data.aetherial_mastery ? data.aetherial_mastery : null,
awakening: {
type: Awakening.toObject(data.awakening.type),
level: data.awakening.level,
},
perpetuity: data.perpetuity,
},
}
}
// Transforms User object into API parameters
export function toParams(data: GridCharacter): GridCharacterParams {
return {
character_id: data.id,
position: data.position,
uncapLevel: data.uncapLevel,
transcendence_step: data.transcendenceStep,
ring1: data.mastery.overMastery[1],
ring2: data.mastery.overMastery[2],
ring3: data.mastery.overMastery[3],
ring4: data.mastery.overMastery[4],
earring: data.mastery.aetherialMastery,
awakening_id: data.mastery.awakening.type.id,
awakening_level: data.mastery.awakening.level,
perpetuity: data.mastery.perpetuity,
}
}

View file

@ -0,0 +1,26 @@
import * as Summon from './SummonTransformer'
// Transforms API response to GridSummon object
export function toObject(data: any): GridSummon {
return {
id: data.id,
object: Summon.toObject(data.object),
position: data.position,
main: data.main,
friend: data.friend,
uncapLevel: data.uncap_level,
transcendenceStep: data.transcendence_step ? data.transcendence_step : null,
quickSummon: data.quick_summon,
}
}
// Transforms User object into API parameters
export function toParams(data: GridSummon): GridSummonParams {
return {
summon_id: data.id,
position: data.position,
uncapLevel: data.uncapLevel,
transcendence_step: data.transcendenceStep,
quick_summon: data.quickSummon,
}
}

View file

@ -0,0 +1,64 @@
import * as GridWeapon from '~transformers/GridWeaponTransformer'
import * as GridSummon from '~transformers/GridSummonTransformer'
import * as GridCharacter from '~transformers/GridCharacterTransformer'
// Transforms API response to Party object
export function toObject(data: any): Grid {
console.log('----- GridTransformer.tsx -----')
console.log(data.summons, data.characters)
console.log('----- End GridTransformer.tsx -----')
const mainSummon = data.summons
? data.summons.find((summon: any) => summon.main === true)
: null
const friendSummon = data.summons
? data.summons.find((summon: any) => summon.friend === true)
: null
const allSummons = data.summons
? removeItem(data.summons, [mainSummon, friendSummon])
: null
const mainWeapon = data.weapons
? data.weapons.find((weapon: any) => weapon.mainhand === true)
: null
return {
characters: data.characters
? mapToGridArrayWithTransformer(data.characters, GridCharacter.toObject)
: null,
summons: {
mainSummon: mainSummon ? GridSummon.toObject(mainSummon) : null,
friendSummon: friendSummon ? GridSummon.toObject(friendSummon) : null,
allSummons: allSummons
? mapToGridArrayWithTransformer(allSummons, GridSummon.toObject)
: null,
},
weapons: {
mainWeapon: mainWeapon ? GridWeapon.toObject(mainWeapon) : null,
allWeapons: data.weapons
? mapToGridArrayWithTransformer(data.weapons, GridWeapon.toObject)
: null,
},
}
}
function removeItem<T>(arr: Array<T>, values: T[]): Array<T> {
values.forEach((value) => {
const index = arr.indexOf(value)
if (index > -1) {
arr.splice(index, 1)
}
})
return arr
}
export function mapToGridArrayWithTransformer<T>(
arr: any[],
transformer: (data: any) => T
): GridArray<T> {
return arr.reduce(
(gridArray, item) => ({ ...gridArray, [item.position]: transformer(item) }),
{} as GridArray<T>
)
}

View file

@ -0,0 +1,45 @@
import * as Awakening from './AwakeningTransformer'
import * as Element from './ElementTransformer'
import * as Weapon from './WeaponTransformer'
import * as WeaponKey from './WeaponKeyTransformer'
// Transforms API response to GridWeapon object
export function toObject(data: any): GridWeapon {
return {
id: data.id,
object: Weapon.toObject(data.object),
position: data.position,
mainhand: data.mainhand,
uncapLevel: data.uncap_level,
element: Element.toObject(data.element),
weaponKeys: data.weapon_keys
? data.weapon_keys.map((key: any) => WeaponKey.toObject(key))
: null,
ax: data.ax ? data.ax : null,
awakening: data.awakening
? {
type: Awakening.toObject(data.awakening.type),
level: data.awakening.awakening_level,
}
: null,
}
}
// Transforms User object into API parameters
export function toParams(data: GridWeapon): GridWeaponParams {
return {
weapon_id: data.id,
position: data.position,
uncapLevel: data.uncapLevel,
element: data.element.id,
weapon_key1_id: data.weaponKeys?.[0].id,
weapon_key2_id: data.weaponKeys?.[1].id,
weapon_key3_id: data.weaponKeys?.[2].id,
ax_modifier1: data.ax?.[0].modifier,
ax_modifier2: data.ax?.[1].modifier,
ax_strength1: data.ax?.[0].strength,
ax_strength2: data.ax?.[1].strength,
awakening_id: data.awakening?.type.id,
awakening_level: data.awakening?.level,
}
}

View file

@ -0,0 +1,15 @@
// Transforms API response to Guidebook object
export function toObject(data: any): Guidebook {
return {
id: data.id,
granblueId: data.granblue_id,
name: {
en: data.name.en,
ja: data.name.jp,
},
description: {
en: data.description.en,
ja: data.description.jp,
},
}
}

View file

@ -0,0 +1,15 @@
import * as Job from './JobTransformer'
// Transforms API response to JobAccessory object
export function toObject(data: any): JobAccessory {
return {
id: data.id,
granblueId: data.granblue_id,
name: {
en: data.name.en,
ja: data.name.jp,
},
job: Job.toObject(data.job),
rarity: data.rarity,
}
}

View file

@ -0,0 +1,20 @@
import * as Job from './JobTransformer'
// Transforms API response to JobSkill object
export function toObject(data: any): JobSkill {
return {
id: data.id,
name: {
en: data.name.en,
ja: data.name.ja,
},
job: Job.toObject(data.job),
slug: data.slug,
color: data.color,
main: data.main,
base: data.base,
sub: data.sub,
emp: data.emp,
order: data.order,
}
}

View file

@ -0,0 +1,21 @@
// Transforms API response to Job object
export function toObject(data: any): Job {
return {
id: data.id,
granblueId: data.granblue_id,
name: {
en: data.name.en,
ja: data.name.ja,
},
row: data.row,
order: data.order,
masterLevel: data.master_level,
ultimateMastery: data.ultimate_mastery,
proficiency: {
proficiency1: data.proficiency?.[0] ?? null,
proficiency2: data.proficiency?.[1] ?? null,
},
accessory: data.accessory,
accessory_type: data.accessory_type,
}
}

View file

@ -0,0 +1,94 @@
import * as Grid from './GridTransformer'
import * as Guidebook from './GuidebookTransformer'
import * as Job from './JobTransformer'
import * as JobAccessory from './JobAccessoryTransformer'
import * as JobSkill from './JobSkillTransformer'
import * as Raid from './RaidTransformer'
import * as User from './UserTransformer'
// Transforms API response to Party object
export function toObject(data: any): Party {
return {
id: data.id,
localId: data.local_id ? data.local_id : null,
editKey: data.edit_key ? data.edit_key : null,
name: data.name,
description: data.description ? data.description : null,
shortcode: data.shortcode,
user: data.user ? User.toObject(data.user) : null,
editable: data.editable ?? false,
grid: Grid.toObject({
characters: data.characters,
summons: data.summons,
weapons: data.weapons,
}),
details: {
extra: data.extra,
fullAuto: data.full_auto,
autoGuard: data.auto_guard,
autoSummon: data.auto_summon,
chargeAttack: data.charge_attack ? data.charge_attack : null,
clearTime: data.clear_time ? data.clear_time : null,
buttonCount: data.button_count ? data.button_count : null,
turnCount: data.turn_count ? data.turn_count : null,
chainCount: data.chain_count ? data.chain_count : null,
},
protagonist: {
job: data.job && Job.toObject(data.job),
skills: data.job_skills
? {
0: data.job_skills[0] && JobSkill.toObject(data.job_skills[0]),
1: data.job_skills[1] && JobSkill.toObject(data.job_skills[1]),
2: data.job_skills[2] && JobSkill.toObject(data.job_skills[2]),
3: data.job_skills[3] && JobSkill.toObject(data.job_skills[3]),
}
: null,
masterLevel: data.master_level ? data.master_level : null,
ultimateMastery: data.ultimate_mastery ? data.ultimate_mastery : null,
accessory: data.accessory ? JobAccessory.toObject(data.accessory) : null,
},
social: {
favorited: data.favorited,
remix: data.remix,
remixes: data.remixes
? data.remixes.map((remix: any) => toObject(remix))
: [],
sourceParty: data.source_party ? toObject(data.source_party) : null,
},
timestamps: {
createdAt: data.created_at,
updatedAt: data.updated_at,
},
raid: data.raid ? Raid.toObject(data.raid) : null,
guidebooks: {
0: data.guidebooks[1] && Guidebook.toObject(data.guidebooks[1]),
1: data.guidebooks[2] && Guidebook.toObject(data.guidebooks[2]),
2: data.guidebooks[3] && Guidebook.toObject(data.guidebooks[3]),
},
}
}
// Transforms Party object into API parameters
export function toParams(party: Party): PartyParams {
return {
local_id: party.localId,
name: party.name,
description: party.description,
extra: party.details.extra,
full_auto: party.details.fullAuto,
auto_guard: party.details.autoGuard,
auto_summon: party.details.autoSummon,
charge_attack: party.details.chargeAttack,
clear_time: party.details.clearTime,
button_count: party.details.buttonCount,
turn_count: party.details.turnCount,
chain_count: party.details.chainCount,
raid_id: party.raid?.id,
job_id: party.protagonist.job?.id,
master_level: party.protagonist.masterLevel,
ultimate_mastery: party.protagonist.ultimateMastery,
guidebook1_id: party.guidebooks[0]?.id,
guidebook2_id: party.guidebooks[1]?.id,
guidebook3_id: party.guidebooks[2]?.id,
} as PartyParams
}

Some files were not shown because too many files have changed in this diff Show more