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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,7 +26,7 @@ const AboutHead = ({ page }: Props) => {
name="description"
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" />
{/* OpenGraph */}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,14 @@
color: inherit;
z-index: 10;
@include breakpoint(phone) {
place-items: flex-end;
overflow-y: hidden;
&.filter {
}
}
.dialogContent {
$multiplier: 4;
@ -51,11 +59,11 @@
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
min-width: inherit;
min-height: 90vh;
min-height: inherit;
transform: initial;
left: 0;
right: 0;
top: 5vh;
top: $unit-10x;
height: auto;
width: 100%;
}
@ -101,110 +109,6 @@
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 {
@ -221,11 +125,20 @@
@keyframes slideUp {
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% {
transform: translate(0, 0%);
transform: translateY(0px);
animation-timing-function: ease-in;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -21,12 +21,6 @@
&.flush {
padding: 0;
}
.Arrow {
fill: var(--dialog-bg);
filter: drop-shadow(0px 1px 1px rgb(0 0 0 / 0.18));
margin-top: -1px;
}
}
.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 {
min-width: $unit * 30;
@include breakpoint(phone) {
width: 100%;
}
}
&.hidden {

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,13 @@ const ToolbarIcon = ({ editor, action, level, icon, onClick }: Props) => {
})
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}>
{icon}
</button>

View file

@ -19,7 +19,7 @@ const GuidebookResult = (props: Props) => {
<li className={styles.result} onClick={props.onClick}>
<img
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}>
<h5>{guidebook.name[locale]}</h5>

View file

@ -99,7 +99,7 @@ const GuidebookUnit = ({
// Methods: Image string generation
function generateImageUrl() {
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)

View file

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

View file

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

View file

@ -11,7 +11,7 @@ const NewHead = () => {
{/* HTML */}
<title>{t('page.titles.new')}</title>
<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" />
{/* OpenGraph */}

View file

@ -20,7 +20,7 @@ const ProfileHead = ({ user }: Props) => {
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" />
{/* OpenGraph */}

View file

@ -9,7 +9,7 @@ const SavedHead = () => {
return (
<Head>
<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" />
<meta property="og:title" content={t('page.titles.saved')} />

View file

@ -11,7 +11,6 @@ const TeamsHead = () => {
{/* HTML */}
<title>{t('page.titles.discover')}</title>
<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" />
{/* OpenGraph */}

View file

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

View file

@ -18,13 +18,13 @@ const JobAccessoryItem = ({ accessory, selected }: Props) => {
return (
<RadioGroup.Item
className="JobAccessoryItem"
className={styles.item}
data-state={selected ? 'checked' : 'unchecked'}
value={accessory.id}
>
<img
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>
</RadioGroup.Item>

View file

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

View file

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

View file

@ -39,8 +39,8 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
// Set current job from state on mount
useEffect(() => {
if (party.job?.id !== '-1') {
setCurrentJob(party.job)
if (party.protagonist.job?.id !== '-1') {
setCurrentJob(party.protagonist.job)
}
}, [])
@ -95,7 +95,7 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
icon={{
alt: item.name[locale],
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={{
alt: currentJob ? currentJob.name[locale] : '',
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}

View file

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

View file

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

View file

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

View file

@ -225,7 +225,7 @@ const AXSelect = (props: Props) => {
if (modifierSet == 0) {
axOptionElements = axOptions.map((ax, i) => {
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]}
</SelectItem>
)

View file

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

View file

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

View file

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

View file

@ -16,7 +16,6 @@ import EditPartyModal from '../EditPartyModal'
import api from '~utils/api'
import { appState } from '~utils/appState'
import { youtube } from '~utils/youtube'
import type { DetailsObject } from 'types'
@ -39,10 +38,7 @@ const PartyFooter = (props: Props) => {
const { t } = useTranslation('common')
const router = useRouter()
const { party: partySnapshot } = useSnapshot(appState)
const youtubeUrlRegex =
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
const { party } = useSnapshot(appState)
// State: Component
const [currentSegment, setCurrentSegment] = useState(0)
@ -55,69 +51,19 @@ const PartyFooter = (props: Props) => {
const [sanitizedDescription, setSanitizedDescription] = useState('')
useEffect(() => {
if (partySnapshot.description) {
const purified = DOMPurify.sanitize(partySnapshot.description)
if (party.description) {
const purified = DOMPurify.sanitize(party.description)
setSanitizedDescription(purified)
} else {
setSanitizedDescription('')
}
}, [partySnapshot.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)
}
}, [party.description])
// Methods: Navigation
function goTo(shortcode?: string) {
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
function toggleFavorite(teamId: string, favorited: boolean) {
if (favorited) unsaveFavorite(teamId)
@ -130,7 +76,7 @@ const PartyFooter = (props: Props) => {
const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index]
party.favorited = true
party.social.favorited = true
let clonedParties = clonedeep(remixes)
clonedParties[index] = party
@ -146,7 +92,7 @@ const PartyFooter = (props: Props) => {
const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index]
party.favorited = false
party.social.favorited = false
let clonedParties = clonedeep(remixes)
clonedParties[index] = party
@ -201,7 +147,9 @@ const PartyFooter = (props: Props) => {
selected={currentSegment === 1}
onClick={() => setCurrentSegment(1)}
>
{t('footer.remixes.label', { count: partySnapshot?.remixes?.length })}
{t('footer.remixes.label', {
count: party?.social.remixes?.length ?? 0,
})}
</Segment>
</SegmentedControl>
)
@ -216,7 +164,7 @@ const PartyFooter = (props: Props) => {
key={props.party?.shortcode}
/>
)}
{(!partySnapshot || !partySnapshot.description) && (
{(!party || !party.description) && (
<section className={styles.noDescription}>
<h3>{t('footer.description.empty')}</h3>
{props.editable && (
@ -240,10 +188,10 @@ const PartyFooter = (props: Props) => {
const remixesSection = (
<section className={styles.remixes}>
{partySnapshot?.remixes?.length > 0 && (
{party?.social.remixes?.length > 0 && (
<GridRepCollection>{renderRemixes()}</GridRepCollection>
)}
{partySnapshot?.remixes?.length === 0 && (
{party?.social.remixes?.length === 0 && (
<div className={styles.noRemixes}>
<h3>{t('footer.remixes.empty')}</h3>
<Button
@ -257,19 +205,19 @@ const PartyFooter = (props: Props) => {
)
function renderRemixes() {
return partySnapshot?.remixes.map((party, i) => {
return party?.social.remixes.map((party, i) => {
return (
<GridRep
id={party.id}
shortcode={party.shortcode}
name={party.name}
createdAt={new Date(party.created_at)}
createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid}
grid={party.weapons}
weapons={party.grid.weapons}
user={party.user}
favorited={party.favorited}
fullAuto={party.full_auto}
autoGuard={party.auto_guard}
favorited={party.social.favorited}
fullAuto={party.details.fullAuto}
autoGuard={party.details.autoGuard}
key={`party-${i}`}
onClick={goTo}
onSave={toggleFavorite}
@ -288,7 +236,7 @@ const PartyFooter = (props: Props) => {
<RemixTeamAlert
creator={props.editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
name={party.name ? party.name : t('no_title')}
open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixTeamCallback}

View file

@ -32,7 +32,7 @@ const PartyHead = ({ party, meta }: Props) => {
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" />
{/* OpenGraph */}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,11 @@
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss'
import classNames from 'classnames'
interface Props {
grid: {
mainWeapon: GridWeapon | undefined
mainWeapon?: GridWeapon | undefined
allWeapons: GridArray<GridWeapon>
}
}
@ -53,14 +52,14 @@ const WeaponRep = (props: Props) => {
let url = ''
if (mainhand && mainhand.object) {
if (mainhand.object.element == 0 && mainhand.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblue_id}_${mainhand.element}.jpg`
if (mainhand.object.element === ElementMap.null && mainhand.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-main/${mainhand.object.granblueId}_${mainhand.element}.jpg`
} 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) {
@ -70,17 +69,17 @@ const WeaponRep = (props: Props) => {
const gridWeapon = grid[position]
if (weapon && gridWeapon) {
if (weapon.element == 0 && gridWeapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg`
if (weapon.element === ElementMap.null && gridWeapon.element) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblueId}_${gridWeapon.element}.jpg`
} 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] ? (
<img alt={weapons[position]?.name[locale]} src={url} />
) : (
''
return (
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 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 WeaponResult from '~components/weapon/WeaponResult'
import SummonResult from '~components/summon/SummonResult'
@ -126,7 +132,7 @@ const SearchModal = (props: Props) => {
if (
!recents.find(
(item) =>
(item as Weapon).granblue_id === (result as Weapon).granblue_id
(item as Weapon).granblueId === (result as Weapon).granblueId
)
) {
recents.unshift(result as Weapon)
@ -136,7 +142,7 @@ const SearchModal = (props: Props) => {
if (
!recents.find(
(item) =>
(item as Summon).granblue_id === (result as Summon).granblue_id
(item as Summon).granblueId === (result as Summon).granblueId
)
) {
recents.unshift(result as Summon)
@ -270,13 +276,14 @@ const SearchModal = (props: Props) => {
const castResults: Weapon[] = results as Weapon[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Weapon) => {
jsx = castResults.map((result: any) => {
const weapon = WeaponTransformer.toObject(result)
return (
<WeaponResult
key={result.id}
data={result}
key={weapon.id}
data={weapon}
onClick={() => {
storeRecentResult(result)
storeRecentResult(weapon)
}}
/>
)
@ -291,13 +298,14 @@ const SearchModal = (props: Props) => {
const castResults: Summon[] = results as Summon[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Summon) => {
jsx = castResults.map((result: any) => {
const summon = SummonTransformer.toObject(result)
return (
<SummonResult
key={result.id}
data={result}
key={summon.id}
data={summon}
onClick={() => {
storeRecentResult(result)
storeRecentResult(summon)
}}
/>
)
@ -312,13 +320,14 @@ const SearchModal = (props: Props) => {
const castResults: Character[] = results as Character[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Character) => {
jsx = castResults.map((result: any) => {
const character = CharacterTransformer.toObject(result)
return (
<CharacterResult
key={result.id}
data={result}
key={character.id}
data={character}
onClick={() => {
storeRecentResult(result)
storeRecentResult(character)
}}
/>
)
@ -334,12 +343,13 @@ const SearchModal = (props: Props) => {
const castResults: JobSkill[] = results as JobSkill[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: JobSkill) => {
const jobSkill = JobSkillTransformer.toObject(result)
return (
<JobSkillResult
key={result.id}
data={result}
key={jobSkill.id}
data={jobSkill}
onClick={() => {
storeRecentResult(result)
storeRecentResult(jobSkill)
}}
/>
)
@ -355,12 +365,13 @@ const SearchModal = (props: Props) => {
const castResults: Guidebook[] = results as Guidebook[]
if (castResults && Object.keys(castResults).length > 0) {
jsx = castResults.map((result: Guidebook) => {
const guidebook = GuidebookTransformer.toObject(result)
return (
<GuidebookResult
key={result.id}
data={result}
key={guidebook.id}
data={guidebook}
onClick={() => {
storeRecentResult(result)
storeRecentResult(guidebook)
}}
/>
)

View file

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

View file

@ -20,14 +20,9 @@ interface Props {
}
const SummonHovercard = (props: Props) => {
const router = useRouter()
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 = Element[props.gridSummon.object.element]
const tintElement = props.gridSummon.object.element.slug
function goTo() {
const urlSafeName = props.gridSummon.object.name.en.replaceAll(' ', '_')
@ -55,19 +50,19 @@ const SummonHovercard = (props: Props) => {
let suffix = ''
if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
props.gridSummon.uncap_level == 5
upgradedSummons.indexOf(summon.granblueId.toString()) != -1 &&
props.gridSummon.uncapLevel == 5
) {
suffix = '_02'
} else if (
props.gridSummon.object.uncap.xlb &&
props.gridSummon.transcendence_step > 0
props.gridSummon.transcendenceStep > 0
) {
suffix = '_03'
}
// 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,20 @@
.Transcendence.Popover {
align-items: center;
.transcendence {
display: flex;
flex-direction: column;
gap: $unit-half;
display: flex;
align-items: center;
justify-content: center;
width: $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;
&.open {
@ -18,7 +27,50 @@
font-weight: $medium;
}
.Pending {
.pending {
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 classNames from 'classnames'
import { Popover } from '@radix-ui/react-popover'
import {
Popover,
PopoverAnchor,
PopoverContent,
} from '~components/common/PopoverContent'
@ -40,12 +40,8 @@ const TranscendencePopover = ({
const popoverRef = React.createRef<HTMLDivElement>()
const classes = classNames({
Transcendence: true,
})
const levelClasses = classNames({
Pending: stage != currentStage,
[styles.pending]: stage != currentStage,
})
useEffect(() => {
@ -77,16 +73,20 @@ const TranscendencePopover = ({
return (
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverAnchor>{children}</PopoverAnchor>
<PopoverContent className={classes} ref={popoverRef} tabIndex={tabIndex}>
<PopoverContent
className={styles.transcendence}
ref={popoverRef}
tabIndex={tabIndex}
>
<TranscendenceStar
className="Interactive Base"
className="interactive base"
editable={true}
interactive={true}
stage={stage}
onFragmentClick={handleFragmentClicked}
onFragmentHover={handleFragmentHovered}
/>
<h4>
<h4 className="name">
<span>{t('level')}&nbsp;</span>
<span className={levelClasses}>{baseLevel + 10 * currentStage}</span>
</h4>

View file

@ -38,7 +38,7 @@ const TranscendenceStar = ({
const starClasses = classnames({
[styles.star]: true,
[styles.immutable]: immutable,
[styles.empty]: stage === 0,
[styles.empty]: true,
[styles.stage1]: stage === 1,
[styles.stage2]: stage === 2,
[styles.stage3]: stage === 3,
@ -46,9 +46,12 @@ const TranscendenceStar = ({
[styles.stage5]: stage === 5,
})
const baseImageClasses = classnames(className, {
[styles.figure]: true,
})
const baseImageClasses = classnames(
{
[styles.figure]: true,
},
className?.split(' ').map((c) => styles[c])
)
useEffect(() => {
setVisibleStage(stage)
@ -87,7 +90,7 @@ const TranscendenceStar = ({
onMouseLeave={interactive ? handleMouseLeave : () => {}}
tabIndex={tabIndex}
>
<div className="Fragments">
<div className={styles.fragments}>
{[...Array(NUM_FRAGMENTS)].map((e, i) => {
const loopStage = i + 1
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 TranscendencePopover from '~components/uncap/TranscendencePopover'
import TranscendenceStar from '~components/uncap/TranscendenceStar'
@ -82,7 +82,11 @@ const UncapIndicator = (props: Props) => {
return props.type === 'character' || props.type === 'summon' ? (
<TranscendencePopover
open={popoverOpen}
stage={props.transcendenceStage ? props.transcendenceStage : 0}
stage={
props.transcendenceStage && props.transcendenceStage !== null
? props.transcendenceStage
: 0
}
type={props.type}
onOpenChange={togglePopover}
sendValue={sendTranscendenceStage}

View file

@ -39,7 +39,7 @@ const WeaponConflictModal = (props: Props) => {
}, [setOpen, props.open])
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) {

View file

@ -18,16 +18,21 @@ import WeaponConflictModal from '~components/weapon/WeaponConflictModal'
import api from '~utils/api'
import { appState } from '~utils/appState'
import * as GridWeaponTransformer from '~transformers/GridWeaponTransformer'
import type { DetailsObject, SearchableObject } from '~types'
import styles from './index.module.scss'
import { ElementMap } from '~utils/elements'
// Props
interface Props {
new: boolean
editable: boolean
weapons?: GridWeapon[]
weapons?: {
mainWeapon?: GridWeapon
allWeapons: GridArray<GridWeapon>
}
guidebooks?: GuidebookList
createParty: (details: DetailsObject) => Promise<Party>
pushHistory?: (path: string) => void
@ -54,7 +59,7 @@ const WeaponGrid = (props: Props) => {
const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false)
// Set up state for view management
const { party, grid } = useSnapshot(appState)
const { party } = useSnapshot(appState)
const [modalOpen, setModalOpen] = useState(false)
// Set up state for conflict management
@ -71,16 +76,19 @@ const WeaponGrid = (props: Props) => {
useEffect(() => {
let initialPreviousUncapValues: { [key: number]: number } = {}
if (appState.grid.weapons.mainWeapon)
if (appState.party.grid.weapons.mainWeapon)
initialPreviousUncapValues[-1] =
appState.grid.weapons.mainWeapon.uncap_level
appState.party.grid.weapons.mainWeapon.uncapLevel
Object.values(appState.grid.weapons.allWeapons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncap_level) : 0
Object.values(appState.party.grid.weapons.allWeapons).map((o) =>
o ? (initialPreviousUncapValues[o.position] = o.uncapLevel) : 0
)
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
function receiveWeaponFromSearch(object: SearchableObject, position: number) {
@ -88,7 +96,7 @@ const WeaponGrid = (props: Props) => {
if (position == 1) appState.party.element = weapon.element
if (!party.id) {
const payload: DetailsObject = { extra: party.extra }
const payload: DetailsObject = { extra: party.details.extra }
props.createParty(payload).then((team) => {
saveWeapon(team.id, weapon, position).then((response) => {
if (response) storeGridWeapon(response.data.grid_weapon)
@ -143,10 +151,10 @@ const WeaponGrid = (props: Props) => {
const position = data.meta['replaced']
if (position == -1) {
appState.grid.weapons.mainWeapon = undefined
appState.party.element = 0
appState.party.grid.weapons.mainWeapon = null
appState.party.element = ElementMap.null
} 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
if (
position === -1 &&
(!appState.grid.weapons.mainWeapon ||
(appState.grid.weapons.mainWeapon &&
appState.grid.weapons.mainWeapon.object.id !== weapon.id))
(!appState.party.grid.weapons.mainWeapon ||
(appState.party.grid.weapons.mainWeapon &&
appState.party.grid.weapons.mainWeapon.object.id !== weapon.id))
) {
post = true
} else if (
position !== -1 &&
(!appState.grid.weapons.allWeapons[position] ||
(appState.grid.weapons.allWeapons[position] &&
appState.grid.weapons.allWeapons[position]?.object.id !== weapon.id))
(!appState.party.grid.weapons.allWeapons[position] ||
(appState.party.grid.weapons.allWeapons[position] &&
appState.party.grid.weapons.allWeapons[position]?.object.id !==
weapon.id))
) {
post = true
}
@ -181,19 +190,20 @@ const WeaponGrid = (props: Props) => {
weapon_id: weapon.id,
position: position,
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) {
appState.grid.weapons.mainWeapon = gridWeapon
appState.party.grid.weapons.mainWeapon = gridWeapon
appState.party.element = gridWeapon.object.element
} else {
// 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) => {
// Remove conflicting characters from state
conflicts.forEach((c) => {
if (appState.grid.weapons.mainWeapon?.object.id === c.object.id) {
appState.grid.weapons.mainWeapon = undefined
appState.party.element = 0
if (
appState.party.grid.weapons.mainWeapon?.object.id === c.object.id
) {
appState.party.grid.weapons.mainWeapon = null
appState.party.element = ElementMap.null
} 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
if (data.position === -1) {
appState.grid.weapons.mainWeapon = undefined
appState.party.grid.weapons.mainWeapon = null
} else {
appState.grid.weapons.allWeapons[response.data.position] = undefined
appState.party.grid.weapons.allWeapons[response.data.position] = null
}
} catch (error) {
console.error(error)
@ -307,13 +319,13 @@ const WeaponGrid = (props: Props) => {
const updateUncapLevel = (position: number, uncapLevel: number) => {
// console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
if (appState.grid.weapons.mainWeapon && position == -1)
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
if (appState.party.grid.weapons.mainWeapon && position == -1)
appState.party.grid.weapons.mainWeapon.uncapLevel = uncapLevel
else {
const weapon = appState.grid.weapons.allWeapons[position]
const weapon = appState.party.grid.weapons.allWeapons[position]
if (weapon) {
weapon.uncap_level = uncapLevel
appState.grid.weapons.allWeapons[position] = weapon
weapon.uncapLevel = uncapLevel
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
let newPreviousValues = { ...previousUncapValues }
if (appState.grid.weapons.mainWeapon && position == -1) {
newPreviousValues[position] = appState.grid.weapons.mainWeapon.uncap_level
if (appState.party.grid.weapons.mainWeapon && position == -1) {
newPreviousValues[position] =
appState.party.grid.weapons.mainWeapon.uncapLevel
} else {
const weapon = appState.grid.weapons.allWeapons[position]
const weapon = appState.party.grid.weapons.allWeapons[position]
if (weapon) {
newPreviousValues[position] = weapon.uncap_level
newPreviousValues[position] = weapon.uncapLevel
} else {
newPreviousValues[position] = 0
}
@ -339,13 +352,13 @@ const WeaponGrid = (props: Props) => {
// Methods: Convenience
const displayExtraContainer =
props.editable ||
appState.party.extra ||
appState.party.details.extra ||
Object.values(appState.party.guidebooks).every((el) => el === undefined)
// Render: JSX components
const mainhandElement = (
<WeaponUnit
gridWeapon={appState.grid.weapons.mainWeapon}
gridWeapon={appState.party.grid.weapons.mainWeapon}
editable={props.editable}
key="grid_mainhand"
position={-1}
@ -358,13 +371,13 @@ const WeaponGrid = (props: Props) => {
const weaponGridElement = Array.from(Array(numWeapons)).map((x, i) => {
const itemClasses = classNames({
Empty: appState.grid.weapons.allWeapons[i] === undefined,
Empty: appState.party.grid.weapons.allWeapons[i] === undefined,
})
return (
<li className={itemClasses} key={`grid_unit_${i}`}>
<WeaponUnit
gridWeapon={appState.grid.weapons.allWeapons[i]}
gridWeapon={party.grid.weapons.allWeapons[i]}
editable={props.editable}
position={i}
unitType={1}
@ -377,13 +390,13 @@ const WeaponGrid = (props: Props) => {
})
const extraElement = () => {
if (appState.party.raid && appState.party.raid.group.extra) {
if (appState.party.raid && appState.party.raid.group?.extra) {
return (
<ExtraContainer>
<ExtraContainerItem title={t('extra_weapons')} className="weapons">
{appState.party.raid && appState.party.raid.group.extra && (
<ExtraWeaponsGrid
grid={appState.grid.weapons.allWeapons}
grid={appState.party.grid.weapons.allWeapons}
editable={props.editable}
offset={numWeapons}
removeWeapon={removeWeapon}
@ -411,30 +424,31 @@ const WeaponGrid = (props: Props) => {
}
const conflictModal = () => {
return incoming && conflicts ? (
<WeaponConflictModal
open={modalOpen}
incomingWeapon={incoming}
conflictingWeapons={conflicts}
desiredPosition={position}
resolveConflict={resolveConflict}
resetConflict={resetConflict}
/>
) : (
''
return (
incoming &&
conflicts && (
<WeaponConflictModal
open={modalOpen}
incomingWeapon={incoming}
conflictingWeapons={conflicts}
desiredPosition={position}
resolveConflict={resolveConflict}
resetConflict={resetConflict}
/>
)
)
}
const incompatibleAlert = () => {
return showIncompatibleAlert ? (
<Alert
open={showIncompatibleAlert}
cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)}
cancelActionText={t('buttons.confirm')}
message={t('alert.incompatible_weapon')}
/>
) : (
''
return (
showIncompatibleAlert && (
<Alert
open={showIncompatibleAlert}
cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)}
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 ax from '~data/ax'
import { ElementMap } from '~utils/elements'
import styles from './index.module.scss'
@ -36,7 +37,6 @@ const WeaponHovercard = (props: Props) => {
const { t } = useTranslation('common')
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const WeaponKeyNames: KeyNames = {
'2': {
en: 'Pendulum',
@ -57,9 +57,10 @@ const WeaponHovercard = (props: Props) => {
}
const tintElement =
props.gridWeapon.object.element == 0 && props.gridWeapon.element
? Element[props.gridWeapon.element]
: Element[props.gridWeapon.object.element]
props.gridWeapon.object.element === ElementMap.null &&
props.gridWeapon.element
? props.gridWeapon.element.slug
: props.gridWeapon.object.element.slug
function goTo() {
const urlSafeName = props.gridWeapon.object.name.en.replaceAll(' ', '_')
@ -76,7 +77,7 @@ const WeaponHovercard = (props: Props) => {
}
const createPrimaryAxSkillString = () => {
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1]
const primaryAxSkills = ax[props.gridWeapon.object.axType - 1]
if (props.gridWeapon.ax) {
const simpleAxSkill = props.gridWeapon.ax[0]
@ -93,7 +94,7 @@ const WeaponHovercard = (props: Props) => {
}
const createSecondaryAxSkillString = () => {
const primaryAxSkills = ax[props.gridWeapon.object.ax_type - 1]
const primaryAxSkills = ax[props.gridWeapon.object.axType - 1]
if (props.gridWeapon.ax) {
const primarySimpleAxSkill = props.gridWeapon.ax[0]
@ -135,27 +136,24 @@ const WeaponHovercard = (props: Props) => {
const keysSection = (
<section className={styles.weaponKeys}>
{WeaponKeyNames[props.gridWeapon.object.series] ? (
{WeaponKeyNames[props.gridWeapon.object.series] && (
<h5 className={tintElement}>
{WeaponKeyNames[props.gridWeapon.object.series][locale]}
{locale === 'en' ? 's' : ''}
</h5>
) : (
''
)}
{props.gridWeapon.weapon_keys
? Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
return (
<div
className={styles.weaponKey}
key={props.gridWeapon.weapon_keys![i].id}
>
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
</div>
)
})
: ''}
{props.gridWeapon.weaponKeys &&
Array.from(Array(props.gridWeapon.weaponKeys?.length)).map((x, i) => {
return (
<div
className={styles.weaponKey}
key={props.gridWeapon.weaponKeys![i].id}
>
<span>{props.gridWeapon.weaponKeys![i].name[locale]}</span>
</div>
)
})}
</section>
)
@ -181,28 +179,26 @@ const WeaponHovercard = (props: Props) => {
</div>
{props.gridWeapon.ax &&
props.gridWeapon.ax[1].modifier &&
props.gridWeapon.ax[1].strength ? (
<div
className={classNames({
[styles.secondary]: true,
[styles.axSkill]: true,
[styles.skill]: true,
})}
>
<div className={styles.axImageWrapper}>
<img
alt="AX2"
src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`}
/>
props.gridWeapon.ax[1].modifier &&
props.gridWeapon.ax[1].strength && (
<div
className={classNames({
[styles.secondary]: true,
[styles.axSkill]: true,
[styles.skill]: true,
})}
>
<div className={styles.axImageWrapper}>
<img
alt="AX2"
src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`}
/>
</div>
<span>{createSecondaryAxSkillString()}</span>
</div>
<span>{createSecondaryAxSkillString()}</span>
</div>
) : (
''
)}
)}
</div>
</section>
)
@ -233,8 +229,8 @@ const WeaponHovercard = (props: Props) => {
props.gridWeapon.ax[0].strength !== undefined &&
axSection}
{props.gridWeapon.awakening && awakeningSection}
{props.gridWeapon.weapon_keys &&
props.gridWeapon.weapon_keys.length > 0 &&
{props.gridWeapon.weaponKeys &&
props.gridWeapon.weaponKeys.length > 0 &&
keysSection}
{wikiButton}
</HovercardContent>

View file

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

View file

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

View file

@ -11,7 +11,6 @@ interface Props {
onClick: () => void
}
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [
'none',
'sword',
@ -36,7 +35,7 @@ const WeaponResult = (props: Props) => {
<li className={styles.result} onClick={props.onClick}>
<img
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}>
<h5>{weapon.name[locale]}</h5>
@ -47,7 +46,7 @@ const WeaponResult = (props: Props) => {
special={false}
/>
<div className={styles.tags}>
<WeaponLabelIcon labelType={Element[weapon.element]} />
<WeaponLabelIcon labelType={weapon.element.slug} />
<WeaponLabelIcon labelType={Proficiency[weapon.proficiency]} />
</div>
</div>

View file

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

View file

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

View file

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

View file

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

14
package-lock.json generated
View file

@ -23,6 +23,7 @@
"@tiptap/extension-highlight": "^2.0.3",
"@tiptap/extension-link": "^2.0.3",
"@tiptap/extension-mention": "^2.0.3",
"@tiptap/extension-placeholder": "^2.0.3",
"@tiptap/extension-typography": "^2.0.3",
"@tiptap/extension-youtube": "^2.0.3",
"@tiptap/pm": "^2.0.3",
@ -7348,6 +7349,19 @@
"@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": {
"version": "2.0.3",
"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-link": "^2.0.3",
"@tiptap/extension-mention": "^2.0.3",
"@tiptap/extension-placeholder": "^2.0.3",
"@tiptap/extension-typography": "^2.0.3",
"@tiptap/extension-youtube": "^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 UserInfo from '~components/filters/UserInfo'
import * as RaidGroupTransformer from '~transformers/RaidGroupTransformer'
import type { AxiosError } from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next'
import type {
@ -175,7 +177,11 @@ const ProfileRoute: React.FC<Props> = ({
function replaceResults(count: number, list: Party[]) {
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 {
setParties([])
}
@ -252,13 +258,13 @@ const ProfileRoute: React.FC<Props> = ({
id={party.id}
shortcode={party.shortcode}
name={party.name}
createdAt={new Date(party.created_at)}
createdAt={new Date(party.timestamps.createdAt)}
raid={party.raid}
grid={party.weapons}
weapons={party.grid.weapons}
user={party.user}
favorited={party.favorited}
fullAuto={party.full_auto}
autoGuard={party.auto_guard}
favorited={party.social.favorited}
fullAuto={party.details.fullAuto}
autoGuard={party.details.autoGuard}
key={`party-${i}`}
onClick={goTo}
/>
@ -331,9 +337,11 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
try {
// Fetch and organize raids
let raidGroups: RaidGroup[] = await api
const raidGroups: RaidGroup[] = await api
.raidGroups()
.then((response) => response.data)
.then((response) =>
response.data.map((group: any) => RaidGroupTransformer.toObject(group))
)
// Create filter object
const filters: FilterObject = extractFilters(query, raidGroups)

View file

@ -1,4 +1,5 @@
import { appWithTranslation } from 'next-i18next'
import Head from 'next/head'
import Link from 'next/link'
import { useTranslation } from 'next-i18next'
import { get } from 'local-storage'
@ -131,20 +132,28 @@ function MyApp({ Component, pageProps }: AppProps) {
}
return (
<ThemeProvider>
<ToastProvider swipeDirection="right">
<TooltipProvider>
<Layout>
{!appState.version ? (
serverUnavailable()
) : (
<Component {...pageProps} />
)}
</Layout>
<Viewport className="ToastViewport" />
</TooltipProvider>
</ToastProvider>
</ThemeProvider>
<>
<Head>
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0"
/>
</Head>
<ThemeProvider>
<ToastProvider swipeDirection="right">
<TooltipProvider>
<Layout>
{!appState.version ? (
serverUnavailable()
) : (
<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 { setHeaders } from '~utils/userToken'
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 type { NextApiRequest, NextApiResponse } from 'next'
@ -90,8 +96,8 @@ const PartyRoute: React.FC<Props> = ({
break
}
if (router.asPath !== '/new' && router.asPath !== '/')
router.replace(path, undefined, { shallow: true })
// if (router.asPath !== '/new' && router.asPath !== '/')
// router.replace(path, undefined, { shallow: true })
}
// Set the initial data from props
@ -126,7 +132,7 @@ const PartyRoute: React.FC<Props> = ({
<React.Fragment key={router.asPath}>
{pageHead()}
<Party
team={context.party}
party={context.party}
selectedTab={selectedTab}
raidGroups={context.raidGroups}
handleTabChanged={handleTabChange}
@ -156,34 +162,43 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
try {
// Fetch and organize raids
let raidGroups: RaidGroup[] = await api
const raidGroups: RaidGroup[] = await api
.raidGroups()
.then((response) => response.data)
.then((response) => response.data.map((group: any) => RaidGroupTransformer.toObject(group)))
// Fetch jobs and job skills
let jobs = await api.endpoints.jobs
const jobs: Job[] = await api.endpoints.jobs
.getAll()
.then((response) => response.data)
.then((response) =>
response.data.map((job: any) => JobTransformer.toObject(job))
)
let jobSkills = await api.allJobSkills()
.then((response) => response.data)
const jobSkills: JobSkill[] = await api
.allJobSkills()
.then((response) =>
response.data.map((skill: any) => JobSkillTransformer.toObject(skill))
)
// Fetch and organize weapon keys
let weaponKeys = await api.endpoints.weapon_keys
const weaponKeys: GroupedWeaponKeys = await api.endpoints.weapon_keys
.getAll()
.then((response) => groupWeaponKeys(response.data))
.then((response) =>
response.data.map((key: any) => WeaponKeyTransformer.toObject(key))
)
.then((keys) => groupWeaponKeys(keys))
// Fetch the party
let party: Party | undefined = undefined
if (query.party) {
let response = await api.endpoints.parties.getOne({
id: query.party,
})
party = response.data.party
} else {
console.error('No party code')
}
if (!query.party) throw new 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
const context: PageContextObj = {
party: party,
@ -206,6 +221,8 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
},
}
} catch (error) {
console.error(error)
// Extract the underlying Axios error
const axiosError = error as AxiosError
const response = axiosError.response

View file

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

View file

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

View file

@ -557,6 +557,19 @@
"tokens": {
"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": {
"copy_url": "Copy the URL to this team",
"new": "Create a new team",

View file

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

View file

@ -159,6 +159,14 @@
--grid-border-color: #{$grid--border--color--light};
// 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-hover: #{$wind--bg--hover--light};
--wind-text: #{$wind--text--light};
@ -373,6 +381,14 @@
--grid-border-color: #{$grid--border--color--dark};
// 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-hover: #{$wind--bg--hover--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--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
$fire--bg--light: $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