Jedmund/image embeds 2 (#424)
## Component Refactors: - Updated `CharacterHovercard` to improve over mastery and awakening section logic. - Refactored `CharacterModal` to streamline state management (rings, awakening, perpetuity) and object preparation. - Adjusted `CharacterUnit` for consistent over mastery handling. - Simplified `AwakeningSelectWithInput` to use awakening slug values and improve error handling. - Updated `RingSelect` to refine ring value syncing and index logic. - Modified `Party` and `PartyHead` to ensure consistent over mastery processing and proper preview URL construction. - Updated `WeaponModal` to align awakening value handling with the new slug-based approach. ## Styling and Configuration: - Improved grid layout and styling in the `WeaponRep` SCSS module. - Updated `next.config.js` rewrite rules to support new preview and character routes. - Added a new API endpoint (`pages/api/preview/[shortcode].tsx`) for fetching party preview images. ## Type Definitions: - Refined types in `types/GridCharacter.d.ts` and `types/index.d.ts` to reflect updated structures for rings, over mastery, and awakening.
This commit is contained in:
parent
eff96e5a37
commit
a02a6c70aa
14 changed files with 293 additions and 232 deletions
5
.aidigestignore
Normal file
5
.aidigestignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
public/images
|
||||||
|
public/labels
|
||||||
|
public/profiles
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
*.log
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -86,3 +86,4 @@ typings/
|
||||||
# DS_Store
|
# DS_Store
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
codebase.md
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ const CharacterHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const overMasterySection = () => {
|
const overMasterySection = () => {
|
||||||
if (props.gridCharacter && props.gridCharacter.over_mastery) {
|
if (props.gridCharacter && props.gridCharacter.over_mastery.length > 0) {
|
||||||
return (
|
return (
|
||||||
<section className={styles.mastery}>
|
<section className={styles.mastery}>
|
||||||
<h5 className={tintElement}>
|
<h5 className={tintElement}>
|
||||||
|
|
@ -73,14 +73,13 @@ const CharacterHovercard = (props: Props) => {
|
||||||
</h5>
|
</h5>
|
||||||
<ul>
|
<ul>
|
||||||
{[...Array(4)].map((e, i) => {
|
{[...Array(4)].map((e, i) => {
|
||||||
const ringIndex = i + 1
|
|
||||||
const ringStat: ExtendedMastery =
|
const ringStat: ExtendedMastery =
|
||||||
props.gridCharacter.over_mastery[ringIndex]
|
props.gridCharacter.over_mastery[i]
|
||||||
|
|
||||||
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
|
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
|
||||||
if (ringIndex === 1 || ringIndex === 2) {
|
if (i === 0 || i === 1) {
|
||||||
return masteryElement(overMastery.a, ringStat)
|
return masteryElement(overMastery.a, ringStat)
|
||||||
} else if (ringIndex === 3) {
|
} else if (i === 2) {
|
||||||
return masteryElement(overMastery.b, ringStat)
|
return masteryElement(overMastery.b, ringStat)
|
||||||
} else {
|
} else {
|
||||||
return masteryElement(overMastery.c, ringStat)
|
return masteryElement(overMastery.c, ringStat)
|
||||||
|
|
@ -96,8 +95,9 @@ const CharacterHovercard = (props: Props) => {
|
||||||
const aetherialMasterySection = () => {
|
const aetherialMasterySection = () => {
|
||||||
if (
|
if (
|
||||||
props.gridCharacter &&
|
props.gridCharacter &&
|
||||||
|
props.gridCharacter.over_mastery &&
|
||||||
props.gridCharacter.aetherial_mastery &&
|
props.gridCharacter.aetherial_mastery &&
|
||||||
props.gridCharacter.aetherial_mastery.modifier > 0
|
props.gridCharacter.aetherial_mastery?.modifier > 0
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<section className={styles.mastery}>
|
<section className={styles.mastery}>
|
||||||
|
|
@ -136,9 +136,8 @@ const CharacterHovercard = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const awakeningSection = () => {
|
const awakeningSection = () => {
|
||||||
|
if (props.gridCharacter.awakening) {
|
||||||
const gridAwakening = props.gridCharacter.awakening
|
const gridAwakening = props.gridCharacter.awakening
|
||||||
|
|
||||||
if (gridAwakening) {
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.awakening}>
|
<section className={styles.awakening}>
|
||||||
<h5 className={tintElement}>
|
<h5 className={tintElement}>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,13 @@ interface Props {
|
||||||
updateCharacter: (object: GridCharacterObject) => Promise<any>
|
updateCharacter: (object: GridCharacterObject) => Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AWAKENING_MAP: { [key: string]: string } = {
|
||||||
|
'character-balanced': 'b1847c82-ece0-4d7a-8af1-c7868d90f34a',
|
||||||
|
'character-atk': '6e233877-8cda-4c8f-a091-3db6f68749e2',
|
||||||
|
'character-def': 'c95441de-f949-4a62-b02b-101aa2e0a638',
|
||||||
|
'character-multi': 'e36b0573-79c3-4dd2-9524-c95def4bbb1a',
|
||||||
|
}
|
||||||
|
|
||||||
const CharacterModal = ({
|
const CharacterModal = ({
|
||||||
gridCharacter,
|
gridCharacter,
|
||||||
children,
|
children,
|
||||||
|
|
@ -64,12 +71,7 @@ const CharacterModal = ({
|
||||||
|
|
||||||
// State: Data
|
// State: Data
|
||||||
const [perpetuity, setPerpetuity] = useState(false)
|
const [perpetuity, setPerpetuity] = useState(false)
|
||||||
const [rings, setRings] = useState<CharacterOverMastery>({
|
const [rings, setRings] = useState<CharacterOverMastery>([])
|
||||||
1: { ...emptyExtendedMastery, modifier: 1 },
|
|
||||||
2: { ...emptyExtendedMastery, modifier: 2 },
|
|
||||||
3: emptyExtendedMastery,
|
|
||||||
4: emptyExtendedMastery,
|
|
||||||
})
|
|
||||||
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
|
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
|
||||||
const [awakening, setAwakening] = useState<Awakening>()
|
const [awakening, setAwakening] = useState<Awakening>()
|
||||||
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
||||||
|
|
@ -94,46 +96,36 @@ const CharacterModal = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gridCharacter.awakening) {
|
||||||
setAwakening(gridCharacter.awakening.type)
|
setAwakening(gridCharacter.awakening.type)
|
||||||
setAwakeningLevel(gridCharacter.awakening.level)
|
setAwakeningLevel(gridCharacter.awakening.level)
|
||||||
|
}
|
||||||
setPerpetuity(gridCharacter.perpetuity)
|
setPerpetuity(gridCharacter.perpetuity)
|
||||||
}, [gridCharacter])
|
}, [gridCharacter])
|
||||||
|
|
||||||
// Prepare the GridWeaponObject to send to the server
|
// Prepare the GridWeaponObject to send to the server
|
||||||
function prepareObject() {
|
function prepareObject(): GridCharacterObject {
|
||||||
let object: GridCharacterObject = {
|
return {
|
||||||
character: {
|
character: {
|
||||||
ring1: {
|
rings: rings, // your local rings array
|
||||||
modifier: rings[1].modifier,
|
|
||||||
strength: rings[1].strength,
|
|
||||||
},
|
|
||||||
ring2: {
|
|
||||||
modifier: rings[2].modifier,
|
|
||||||
strength: rings[2].strength,
|
|
||||||
},
|
|
||||||
ring3: {
|
|
||||||
modifier: rings[3].modifier,
|
|
||||||
strength: rings[3].strength,
|
|
||||||
},
|
|
||||||
ring4: {
|
|
||||||
modifier: rings[4].modifier,
|
|
||||||
strength: rings[4].strength,
|
|
||||||
},
|
|
||||||
earring: {
|
earring: {
|
||||||
modifier: earring.modifier,
|
modifier: earring.modifier,
|
||||||
strength: earring.strength,
|
strength:
|
||||||
|
earring.modifier && earring.modifier > 0 ? earring.strength : 0,
|
||||||
},
|
},
|
||||||
|
// Only include awakening if one is set.
|
||||||
|
...(awakening
|
||||||
|
? {
|
||||||
|
awakening: {
|
||||||
|
id: awakening.id,
|
||||||
|
level: awakeningLevel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
transcendence_step: transcendenceStep,
|
transcendence_step: transcendenceStep,
|
||||||
perpetuity: perpetuity,
|
perpetuity: perpetuity,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (awakening) {
|
|
||||||
object.character.awakening_id = awakening.id
|
|
||||||
object.character.awakening_level = awakeningLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
return object
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Modification checking
|
// Methods: Modification checking
|
||||||
|
|
@ -152,12 +144,12 @@ const CharacterModal = ({
|
||||||
|
|
||||||
function ringsChanged() {
|
function ringsChanged() {
|
||||||
// Create an empty ExtendedMastery object
|
// Create an empty ExtendedMastery object
|
||||||
const emptyRingset: CharacterOverMastery = {
|
const emptyRingset: CharacterOverMastery = [
|
||||||
1: { ...emptyExtendedMastery, modifier: 1 },
|
{ ...emptyExtendedMastery, modifier: 1 },
|
||||||
2: { ...emptyExtendedMastery, modifier: 2 },
|
{ ...emptyExtendedMastery, modifier: 2 },
|
||||||
3: emptyExtendedMastery,
|
emptyExtendedMastery,
|
||||||
4: emptyExtendedMastery,
|
emptyExtendedMastery,
|
||||||
}
|
]
|
||||||
|
|
||||||
// Check if the current ringset is empty on the current GridCharacter and our local state
|
// Check if the current ringset is empty on the current GridCharacter and our local state
|
||||||
const isEmptyRingset =
|
const isEmptyRingset =
|
||||||
|
|
@ -195,8 +187,8 @@ const CharacterModal = ({
|
||||||
function awakeningChanged() {
|
function awakeningChanged() {
|
||||||
// Check if the awakening in local state is different from the one on the current GridCharacter
|
// Check if the awakening in local state is different from the one on the current GridCharacter
|
||||||
const awakeningChanged =
|
const awakeningChanged =
|
||||||
!isEqual(gridCharacter.awakening.type, awakening) ||
|
!isEqual(gridCharacter.awakening?.type, awakening) ||
|
||||||
gridCharacter.awakening.level !== awakeningLevel
|
gridCharacter.awakening?.level !== awakeningLevel
|
||||||
|
|
||||||
// Return true if the awakening has been modified and is not empty
|
// Return true if the awakening has been modified and is not empty
|
||||||
return awakeningChanged
|
return awakeningChanged
|
||||||
|
|
@ -227,8 +219,26 @@ const CharacterModal = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveAwakeningValues(id: string, level: number) {
|
function receiveAwakeningValues(slug: string, level: number) {
|
||||||
setAwakening(gridCharacter.object.awakenings.find((a) => a.id === id))
|
const mappedId = AWAKENING_MAP[slug] || null
|
||||||
|
const existingAwakening = gridCharacter.object.awakenings.find(
|
||||||
|
(a) => a.slug === slug
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingAwakening && mappedId) {
|
||||||
|
setAwakening({
|
||||||
|
...existingAwakening,
|
||||||
|
id: mappedId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setAwakening({
|
||||||
|
id: mappedId || '',
|
||||||
|
slug,
|
||||||
|
name: { en: '', jp: '' },
|
||||||
|
order: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setAwakeningLevel(level)
|
setAwakeningLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,13 +317,13 @@ const CharacterModal = ({
|
||||||
object="earring"
|
object="earring"
|
||||||
dataSet={elementalizeAetherialMastery(gridCharacter)}
|
dataSet={elementalizeAetherialMastery(gridCharacter)}
|
||||||
selectValue={
|
selectValue={
|
||||||
gridCharacter.aetherial_mastery
|
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
|
||||||
? gridCharacter.aetherial_mastery.modifier
|
? gridCharacter.aetherial_mastery?.modifier
|
||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
inputValue={
|
inputValue={
|
||||||
gridCharacter.aetherial_mastery
|
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
|
||||||
? gridCharacter.aetherial_mastery.strength
|
? gridCharacter.aetherial_mastery?.strength
|
||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
sendValidity={receiveValidity}
|
sendValidity={receiveValidity}
|
||||||
|
|
|
||||||
|
|
@ -148,12 +148,12 @@ const CharacterUnit = ({
|
||||||
let character = cloneDeep(gridCharacter)
|
let character = cloneDeep(gridCharacter)
|
||||||
|
|
||||||
if (character.over_mastery) {
|
if (character.over_mastery) {
|
||||||
const overMastery: CharacterOverMastery = {
|
const overMastery: CharacterOverMastery = [
|
||||||
1: gridCharacter.over_mastery[0],
|
gridCharacter.over_mastery[0],
|
||||||
2: gridCharacter.over_mastery[1],
|
gridCharacter.over_mastery[1],
|
||||||
3: gridCharacter.over_mastery[2],
|
gridCharacter.over_mastery[2],
|
||||||
4: gridCharacter.over_mastery[3],
|
gridCharacter.over_mastery[3],
|
||||||
}
|
]
|
||||||
|
|
||||||
character.over_mastery = overMastery
|
character.over_mastery = overMastery
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// Core dependencies
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
@ -73,7 +72,8 @@ const AwakeningSelectWithInput = ({
|
||||||
setCurrentAwakening(awakening)
|
setCurrentAwakening(awakening)
|
||||||
setCurrentLevel(level ? level : 1)
|
setCurrentLevel(level ? level : 1)
|
||||||
|
|
||||||
if (awakening) sendValidity(true)
|
// If there is an awakening (even if it's the default) we consider the field valid.
|
||||||
|
if (awakening || defaultAwakening) sendValidity(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Methods: UI state management
|
// Methods: UI state management
|
||||||
|
|
@ -90,35 +90,32 @@ const AwakeningSelectWithInput = ({
|
||||||
|
|
||||||
// Methods: Rendering
|
// Methods: Rendering
|
||||||
function generateOptions() {
|
function generateOptions() {
|
||||||
const sortedDataSet = [...dataSet].sort((a, b) => {
|
const sortedDataSet = [...dataSet].sort((a, b) => a.order - b.order)
|
||||||
return a.order - b.order
|
let options: React.ReactNode[] = sortedDataSet.map((awakening) =>
|
||||||
})
|
generateItem(awakening)
|
||||||
|
)
|
||||||
let options: React.ReactNode[] = sortedDataSet.map((awakening, i) => {
|
|
||||||
return generateItem(awakening)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!dataSet.includes(defaultAwakening))
|
if (!dataSet.includes(defaultAwakening))
|
||||||
options.unshift(generateItem(defaultAwakening))
|
options.unshift(generateItem(defaultAwakening))
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateItem(awakening: Awakening) {
|
function generateItem(awakening: Awakening) {
|
||||||
return (
|
return (
|
||||||
<SelectItem key={awakening.slug} value={awakening.id}>
|
<SelectItem key={awakening.slug} value={awakening.slug}>
|
||||||
{awakening.name[locale]}
|
{awakening.name[locale]}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: User input detection
|
// Methods: User input detection
|
||||||
function handleSelectChange(id: string) {
|
function handleSelectChange(value: string) {
|
||||||
|
// Here, value is the awakening slug.
|
||||||
const input = inputRef.current
|
const input = inputRef.current
|
||||||
if (input && !handleInputError(parseFloat(input.value))) return
|
if (input && !handleInputError(parseFloat(input.value))) return
|
||||||
|
|
||||||
setCurrentAwakening(dataSet.find((awakening) => awakening.id === id))
|
const selectedAwakening = dataSet.find((a) => a.slug === value)
|
||||||
sendValues(id, currentLevel)
|
setCurrentAwakening(selectedAwakening)
|
||||||
|
sendValues(value, currentLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
|
@ -127,7 +124,10 @@ const AwakeningSelectWithInput = ({
|
||||||
|
|
||||||
const newLevel = parseInt(event.target.value)
|
const newLevel = parseInt(event.target.value)
|
||||||
setCurrentLevel(newLevel)
|
setCurrentLevel(newLevel)
|
||||||
sendValues(currentAwakening ? currentAwakening.id : '0', newLevel)
|
sendValues(
|
||||||
|
currentAwakening ? currentAwakening.slug : defaultAwakening.slug,
|
||||||
|
newLevel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Handle error
|
// Methods: Handle error
|
||||||
|
|
@ -135,16 +135,12 @@ const AwakeningSelectWithInput = ({
|
||||||
let error = ''
|
let error = ''
|
||||||
|
|
||||||
if (currentAwakening) {
|
if (currentAwakening) {
|
||||||
if (value && value % 1 != 0) {
|
if (value && value % 1 !== 0) {
|
||||||
error = t(`awakening.errors.value_not_whole`)
|
error = t(`awakening.errors.value_not_whole`)
|
||||||
} else if (value < 1) {
|
} else if (value < 1) {
|
||||||
error = t(`awakening.errors.value_too_low`, {
|
error = t(`awakening.errors.value_too_low`, { minValue: 1 })
|
||||||
minValue: 1,
|
|
||||||
})
|
|
||||||
} else if (value > maxLevel) {
|
} else if (value > maxLevel) {
|
||||||
error = t(`awakening.errors.value_too_high`, {
|
error = t(`awakening.errors.value_too_high`, { maxValue: maxLevel })
|
||||||
maxValue: maxLevel,
|
|
||||||
})
|
|
||||||
} else if (!value || value <= 0) {
|
} else if (!value || value <= 0) {
|
||||||
error = t(`awakening.errors.value_empty`)
|
error = t(`awakening.errors.value_empty`)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -165,13 +161,9 @@ const AwakeningSelectWithInput = ({
|
||||||
|
|
||||||
const rangeString = () => {
|
const rangeString = () => {
|
||||||
let placeholder = ''
|
let placeholder = ''
|
||||||
|
if (currentAwakening) {
|
||||||
if (awakening) {
|
placeholder = `1~${maxLevel}`
|
||||||
const minValue = 1
|
|
||||||
const maxValue = maxLevel
|
|
||||||
placeholder = `${minValue}~${maxValue}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return placeholder
|
return placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +172,8 @@ const AwakeningSelectWithInput = ({
|
||||||
<div className={styles.set}>
|
<div className={styles.set}>
|
||||||
<Select
|
<Select
|
||||||
key="awakening-type"
|
key="awakening-type"
|
||||||
value={`${awakening ? awakening.id : defaultAwakening.id}`}
|
// Use the slug as the value
|
||||||
|
value={`${awakening ? awakening.slug : defaultAwakening.slug}`}
|
||||||
open={open}
|
open={open}
|
||||||
disabled={selectDisabled}
|
disabled={selectDisabled}
|
||||||
onValueChange={handleSelectChange}
|
onValueChange={handleSelectChange}
|
||||||
|
|
@ -200,7 +193,8 @@ const AwakeningSelectWithInput = ({
|
||||||
className={inputClasses}
|
className={inputClasses}
|
||||||
fieldsetClassName={classNames({
|
fieldsetClassName={classNames({
|
||||||
hidden:
|
hidden:
|
||||||
currentAwakening === undefined || currentAwakening.id === '0',
|
currentAwakening === undefined ||
|
||||||
|
currentAwakening.slug === defaultAwakening.slug,
|
||||||
})}
|
})}
|
||||||
wrapperClassName="fullHeight"
|
wrapperClassName="fullHeight"
|
||||||
bound={true}
|
bound={true}
|
||||||
|
|
|
||||||
|
|
@ -25,21 +25,21 @@ interface Props {
|
||||||
|
|
||||||
const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
||||||
// Ring value states
|
// Ring value states
|
||||||
const [rings, setRings] = useState<CharacterOverMastery>({
|
const [rings, setRings] = useState<CharacterOverMastery>([
|
||||||
1: { ...emptyRing, modifier: 1 },
|
{ ...emptyRing, modifier: 1 },
|
||||||
2: { ...emptyRing, modifier: 2 },
|
{ ...emptyRing, modifier: 2 },
|
||||||
3: emptyRing,
|
emptyRing,
|
||||||
4: emptyRing,
|
emptyRing,
|
||||||
})
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gridCharacter.over_mastery) {
|
if (gridCharacter.over_mastery) {
|
||||||
setRings({
|
setRings([
|
||||||
1: gridCharacter.over_mastery[1],
|
gridCharacter.over_mastery[0],
|
||||||
2: gridCharacter.over_mastery[2],
|
gridCharacter.over_mastery[1],
|
||||||
3: gridCharacter.over_mastery[3],
|
gridCharacter.over_mastery[2],
|
||||||
4: gridCharacter.over_mastery[4],
|
gridCharacter.over_mastery[3],
|
||||||
})
|
])
|
||||||
}
|
}
|
||||||
}, [gridCharacter])
|
}, [gridCharacter])
|
||||||
|
|
||||||
|
|
@ -64,13 +64,13 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 1:
|
case 0:
|
||||||
return overMastery.a ? [overMastery.a[0]] : []
|
return overMastery.a ? [overMastery.a[0]] : []
|
||||||
case 2:
|
case 1:
|
||||||
return overMastery.a ? [overMastery.a[1]] : []
|
return overMastery.a ? [overMastery.a[1]] : []
|
||||||
case 3:
|
case 2:
|
||||||
return overMastery.b ? [noValue, ...overMastery.b] : []
|
return overMastery.b ? [noValue, ...overMastery.b] : []
|
||||||
case 4:
|
case 3:
|
||||||
return overMastery.c ? [noValue, ...overMastery.c] : []
|
return overMastery.c ? [noValue, ...overMastery.c] : []
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
|
|
@ -78,72 +78,74 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveRingValues(index: number, left: number, right: number) {
|
function receiveRingValues(index: number, left: number, right: number) {
|
||||||
// console.log(`Receiving values from ${index}: ${left} ${right}`)
|
if (index === 0 || index === 1) {
|
||||||
if (index == 1 || index == 2) {
|
// For rings 1 and 2 (indices 0 and 1), update using the synced function.
|
||||||
setSyncedRingValues(index, right)
|
setSyncedRingValues(index as 0 | 1, right)
|
||||||
} else if (index == 3 && left == 0) {
|
} else if (index === 2 && left === 0) {
|
||||||
setRings({
|
// If ring 3 (index 2) is being unset (left is 0), then also unset ring 4.
|
||||||
...rings,
|
setRings((prev) => {
|
||||||
3: {
|
const newRings = [...prev]
|
||||||
modifier: 0,
|
newRings[2] = { modifier: 0, strength: 0 }
|
||||||
strength: 0,
|
newRings[3] = { modifier: 0, strength: 0 }
|
||||||
},
|
return newRings
|
||||||
4: {
|
|
||||||
modifier: 0,
|
|
||||||
strength: 0,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setRings({
|
// For any other case (including ring 4 being unset), update only that ring.
|
||||||
...rings,
|
setRings((prev) => {
|
||||||
[index]: {
|
const newRings = [...prev]
|
||||||
modifier: left,
|
newRings[index] = { modifier: left, strength: right }
|
||||||
strength: right,
|
return newRings
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSyncedRingValues(index: 1 | 2, value: number) {
|
function setSyncedRingValues(changedIndex: 0 | 1, newStrength: number) {
|
||||||
// console.log(`Setting synced value for ${index} with value ${value}`)
|
// Assume dataSet(0) holds the attack-related data and dataSet(1) holds the HP-related data.
|
||||||
const atkValues = (dataSet(1)[0] as ItemSkill).values ?? []
|
// (Adjust these calls if your datasets are in different positions.)
|
||||||
const hpValues = (dataSet(2)[0] as ItemSkill).values ?? []
|
const attackItem = dataSet(0)[0] as ItemSkill
|
||||||
|
const hpItem = dataSet(1)[0] as ItemSkill
|
||||||
|
|
||||||
const found =
|
const attackValues: number[] = attackItem.values ?? []
|
||||||
index === 1 ? atkValues.indexOf(value) : hpValues.indexOf(value)
|
const hpValues: number[] = hpItem.values ?? []
|
||||||
const atkValue = atkValues[found] ?? 0
|
|
||||||
const hpValue = hpValues[found] ?? 0
|
|
||||||
|
|
||||||
setRings({
|
// Determine the index based on which ring changed:
|
||||||
...rings,
|
const selectedIndex =
|
||||||
1: {
|
changedIndex === 0
|
||||||
modifier: 1,
|
? attackValues.indexOf(newStrength)
|
||||||
strength: atkValue,
|
: hpValues.indexOf(newStrength)
|
||||||
},
|
|
||||||
2: {
|
// If the new strength value isn’t found, do nothing.
|
||||||
modifier: 2,
|
if (selectedIndex === -1) {
|
||||||
strength: hpValue,
|
return
|
||||||
},
|
}
|
||||||
|
|
||||||
|
// Get the corresponding values for both rings.
|
||||||
|
const newAttackValue = attackValues[selectedIndex] ?? 0
|
||||||
|
const newHpValue = hpValues[selectedIndex] ?? 0
|
||||||
|
|
||||||
|
// Update both ring values simultaneously.
|
||||||
|
setRings((prev) => {
|
||||||
|
const newRings = [...prev]
|
||||||
|
newRings[0] = { modifier: 1, strength: newAttackValue }
|
||||||
|
newRings[1] = { modifier: 2, strength: newHpValue }
|
||||||
|
return newRings
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.rings}>
|
<div className={styles.rings}>
|
||||||
{[...Array(4)].map((e, i) => {
|
{rings.map((ringStat, i) => {
|
||||||
const index = i + 1
|
|
||||||
const ringStat = rings[index]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExtendedMasterySelect
|
<ExtendedMasterySelect
|
||||||
name={`ring-${index}`}
|
name={`ring-${i}`}
|
||||||
object="ring"
|
object="ring"
|
||||||
key={`ring-${index}`}
|
key={`ring-${i}`}
|
||||||
dataSet={dataSet(index)}
|
dataSet={dataSet(i)}
|
||||||
leftSelectDisabled={index === 1 || index === 2}
|
leftSelectDisabled={i === 0 || i === 1}
|
||||||
leftSelectValue={ringStat.modifier ? ringStat.modifier : 0}
|
leftSelectValue={ringStat?.modifier ?? 0}
|
||||||
rightSelectValue={ringStat.strength ? ringStat.strength : 0}
|
rightSelectValue={ringStat?.strength ?? 0}
|
||||||
sendValues={(left: number, right: number) => {
|
sendValues={(left: number, right: number) => {
|
||||||
receiveRingValues(index, left, right)
|
receiveRingValues(i, left, right)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -324,13 +324,13 @@ const Party = (props: Props) => {
|
||||||
list.forEach((object: GridCharacter) => {
|
list.forEach((object: GridCharacter) => {
|
||||||
let character = clonedeep(object)
|
let character = clonedeep(object)
|
||||||
|
|
||||||
if (character.over_mastery) {
|
if (character.over_mastery && character.over_mastery) {
|
||||||
const overMastery: CharacterOverMastery = {
|
const overMastery: CharacterOverMastery = [
|
||||||
1: object.over_mastery[0],
|
object.over_mastery[0],
|
||||||
2: object.over_mastery[1],
|
object.over_mastery[1],
|
||||||
3: object.over_mastery[2],
|
object.over_mastery[2],
|
||||||
4: object.over_mastery[3],
|
object.over_mastery[3],
|
||||||
}
|
]
|
||||||
|
|
||||||
character.over_mastery = overMastery
|
character.over_mastery = overMastery
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const PartyHead = ({ party, meta }: Props) => {
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
const previewUrl = `${
|
const previewUrl = `${
|
||||||
process.env.NEXT_PUBLIC_SITE_URL || 'https://granblue.team'
|
process.env.NEXT_PUBLIC_SITE_URL || 'https://granblue.team'
|
||||||
}/preview/${party.shortcode}`
|
}/p/${party.shortcode}/preview`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Head>
|
<Head>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,67 @@
|
||||||
|
// Overall container – never taller than $rep-height:
|
||||||
.rep {
|
.rep {
|
||||||
aspect-ratio: 2/0.955;
|
|
||||||
border-radius: $card-corner;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 3.39fr; /* left column takes up 1 fraction, right column takes up 3 fractions */
|
|
||||||
grid-gap: $unit-half; /* add a gap of 8px between grid items */
|
|
||||||
height: $rep-height;
|
height: $rep-height;
|
||||||
transition: $duration-opacity-fade opacity ease-in;
|
display: grid;
|
||||||
opacity: 0.5;
|
// First column: mainhand width = $rep-height * (200/420)
|
||||||
|
// Second column: weapons grid – its width will be auto (we calculate it below)
|
||||||
@include breakpoint(small-tablet) {
|
grid-template-columns:
|
||||||
display: none;
|
calc(#{$rep-height} * (200 / 420))
|
||||||
|
auto;
|
||||||
|
gap: $unit-half;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainhand,
|
/* --- Mainhand image --- */
|
||||||
|
.mainhand {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%; // takes the grid column’s computed width
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain; // or "cover" if you prefer cropping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Weapons grid --- */
|
||||||
|
.weapons {
|
||||||
|
/* Reset default UL spacing */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
// We know there will be 3 columns and 3 rows.
|
||||||
|
// Each row's height is one-third of $rep-height:
|
||||||
|
// Subtract the 2 vertical gaps from the total height before dividing:
|
||||||
|
grid-template-rows: repeat(
|
||||||
|
3,
|
||||||
|
calc((#{$rep-height} - (2 * #{$unit-half})) / 3)
|
||||||
|
);
|
||||||
|
// Each column's width is calculated as: (cell height * (280/160))
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
3,
|
||||||
|
calc((#{$rep-height} - (2 * #{$unit-half})) / 3 * (280 / 160))
|
||||||
|
);
|
||||||
|
gap: $unit-half;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each grid cell (a .weapon) */
|
||||||
.weapon {
|
.weapon {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
// Center the image (or placeholder) within the cell:
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
img[src*='jpg'] {
|
img {
|
||||||
border-radius: 4px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.mainhand {
|
|
||||||
aspect-ratio: 73/153;
|
|
||||||
display: grid;
|
|
||||||
grid-column: 1 / 2; /* spans one column */
|
|
||||||
}
|
|
||||||
|
|
||||||
.weapons {
|
|
||||||
display: grid; /* make the right-images container a grid */
|
|
||||||
grid-template-columns: repeat(
|
|
||||||
3,
|
|
||||||
1fr
|
|
||||||
); /* create 3 columns, each taking up 1 fraction */
|
|
||||||
grid-template-rows: repeat(
|
|
||||||
3,
|
|
||||||
1fr
|
|
||||||
); /* create 3 rows, each taking up 1 fraction */
|
|
||||||
gap: $unit-half;
|
|
||||||
// column-gap: $unit;
|
|
||||||
// row-gap: $unit-2x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weapon {
|
|
||||||
aspect-ratio: 280 / 160;
|
|
||||||
display: grid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,13 +136,12 @@ const WeaponModal = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive values from AwakeningSelectWithInput
|
// Receive values from AwakeningSelectWithInput
|
||||||
function receiveAwakeningValues(id: string, level: number) {
|
function receiveAwakeningValues(slug: string, level: number) {
|
||||||
setAwakening(gridWeapon.object.awakenings.find((a) => a.id === id))
|
// Look up the awakening by its slug, since the select sends a slug.
|
||||||
console.log(level)
|
setAwakening(gridWeapon.object.awakenings.find((a) => a.slug === slug))
|
||||||
setAwakeningLevel(level)
|
setAwakeningLevel(level)
|
||||||
setFormValid(true)
|
setFormValid(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive values from WeaponKeySelect
|
// Receive values from WeaponKeySelect
|
||||||
function receiveWeaponKey(value: WeaponKey, slot: number) {
|
function receiveWeaponKey(value: WeaponKey, slot: number) {
|
||||||
if (slot === 0) setWeaponKey1(value)
|
if (slot === 0) setWeaponKey1(value)
|
||||||
|
|
|
||||||
33
pages/api/preview/[shortcode].tsx
Normal file
33
pages/api/preview/[shortcode].tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const { shortcode } = req.query
|
||||||
|
|
||||||
|
if (!shortcode || Array.isArray(shortcode)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid shortcode' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${process.env.NEXT_PUBLIC_SIERO_API_URL}/parties/${shortcode}/preview`,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
headers: {
|
||||||
|
Accept: 'image/png',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set correct content type and caching headers
|
||||||
|
res.setHeader('Content-Type', 'image/png')
|
||||||
|
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
||||||
|
|
||||||
|
return res.send(response.data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching preview:', error)
|
||||||
|
return res.status(500).json({ error: 'Failed to fetch preview' })
|
||||||
|
}
|
||||||
|
}
|
||||||
6
types/GridCharacter.d.ts
vendored
6
types/GridCharacter.d.ts
vendored
|
|
@ -4,11 +4,11 @@ interface GridCharacter {
|
||||||
object: Character
|
object: Character
|
||||||
uncap_level: number
|
uncap_level: number
|
||||||
transcendence_step: number
|
transcendence_step: number
|
||||||
over_mastery: CharacterOverMastery
|
perpetuity: boolean
|
||||||
aetherial_mastery: ExtendedMastery
|
over_mastery: ExtendedMastery[]
|
||||||
|
aetherial_mastery?: ExtendedMastery
|
||||||
awakening: {
|
awakening: {
|
||||||
type: Awakening
|
type: Awakening
|
||||||
level: number
|
level: number
|
||||||
}
|
}
|
||||||
perpetuity: boolean
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
types/index.d.ts
vendored
28
types/index.d.ts
vendored
|
|
@ -48,23 +48,25 @@ export type ExtendedMastery = {
|
||||||
strength?: number
|
strength?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CharacterOverMastery = {
|
export type CharacterOverMastery = ExtendedMastery[]
|
||||||
[key: number]: ExtendedMastery
|
|
||||||
1: ExtendedMastery
|
export interface MasteryBonuses {
|
||||||
2: ExtendedMastery
|
awakening?: {
|
||||||
3: ExtendedMastery
|
type: Awakening
|
||||||
4: ExtendedMastery
|
level: number
|
||||||
|
}
|
||||||
|
over_mastery?: CharacterOverMastery
|
||||||
|
aetherial_mastery?: ExtendedMastery
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GridCharacterObject {
|
export interface GridCharacterObject {
|
||||||
character: {
|
character: {
|
||||||
ring1: ExtendedMastery
|
rings: ExtendedMastery[]
|
||||||
ring2: ExtendedMastery
|
|
||||||
ring3: ExtendedMastery
|
|
||||||
ring4: ExtendedMastery
|
|
||||||
earring: ExtendedMastery
|
earring: ExtendedMastery
|
||||||
awakening_id?: string
|
awakening?: {
|
||||||
awakening_level?: number
|
id: string
|
||||||
|
level: number
|
||||||
|
}
|
||||||
transcendence_step: number
|
transcendence_step: number
|
||||||
perpetuity: boolean
|
perpetuity: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue