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
|
||||
*.tsbuildinfo
|
||||
codebase.md
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const CharacterHovercard = (props: Props) => {
|
|||
}
|
||||
|
||||
const overMasterySection = () => {
|
||||
if (props.gridCharacter && props.gridCharacter.over_mastery) {
|
||||
if (props.gridCharacter && props.gridCharacter.over_mastery.length > 0) {
|
||||
return (
|
||||
<section className={styles.mastery}>
|
||||
<h5 className={tintElement}>
|
||||
|
|
@ -73,14 +73,13 @@ const CharacterHovercard = (props: Props) => {
|
|||
</h5>
|
||||
<ul>
|
||||
{[...Array(4)].map((e, i) => {
|
||||
const ringIndex = i + 1
|
||||
const ringStat: ExtendedMastery =
|
||||
props.gridCharacter.over_mastery[ringIndex]
|
||||
props.gridCharacter.over_mastery[i]
|
||||
|
||||
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
|
||||
if (ringIndex === 1 || ringIndex === 2) {
|
||||
if (i === 0 || i === 1) {
|
||||
return masteryElement(overMastery.a, ringStat)
|
||||
} else if (ringIndex === 3) {
|
||||
} else if (i === 2) {
|
||||
return masteryElement(overMastery.b, ringStat)
|
||||
} else {
|
||||
return masteryElement(overMastery.c, ringStat)
|
||||
|
|
@ -96,8 +95,9 @@ const CharacterHovercard = (props: Props) => {
|
|||
const aetherialMasterySection = () => {
|
||||
if (
|
||||
props.gridCharacter &&
|
||||
props.gridCharacter.over_mastery &&
|
||||
props.gridCharacter.aetherial_mastery &&
|
||||
props.gridCharacter.aetherial_mastery.modifier > 0
|
||||
props.gridCharacter.aetherial_mastery?.modifier > 0
|
||||
) {
|
||||
return (
|
||||
<section className={styles.mastery}>
|
||||
|
|
@ -136,9 +136,8 @@ const CharacterHovercard = (props: Props) => {
|
|||
}
|
||||
|
||||
const awakeningSection = () => {
|
||||
const gridAwakening = props.gridCharacter.awakening
|
||||
|
||||
if (gridAwakening) {
|
||||
if (props.gridCharacter.awakening) {
|
||||
const gridAwakening = props.gridCharacter.awakening
|
||||
return (
|
||||
<section className={styles.awakening}>
|
||||
<h5 className={tintElement}>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ interface Props {
|
|||
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 = ({
|
||||
gridCharacter,
|
||||
children,
|
||||
|
|
@ -64,12 +71,7 @@ const CharacterModal = ({
|
|||
|
||||
// State: Data
|
||||
const [perpetuity, setPerpetuity] = useState(false)
|
||||
const [rings, setRings] = useState<CharacterOverMastery>({
|
||||
1: { ...emptyExtendedMastery, modifier: 1 },
|
||||
2: { ...emptyExtendedMastery, modifier: 2 },
|
||||
3: emptyExtendedMastery,
|
||||
4: emptyExtendedMastery,
|
||||
})
|
||||
const [rings, setRings] = useState<CharacterOverMastery>([])
|
||||
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
|
||||
const [awakening, setAwakening] = useState<Awakening>()
|
||||
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
||||
|
|
@ -94,46 +96,36 @@ const CharacterModal = ({
|
|||
})
|
||||
}
|
||||
|
||||
setAwakening(gridCharacter.awakening.type)
|
||||
setAwakeningLevel(gridCharacter.awakening.level)
|
||||
if (gridCharacter.awakening) {
|
||||
setAwakening(gridCharacter.awakening.type)
|
||||
setAwakeningLevel(gridCharacter.awakening.level)
|
||||
}
|
||||
setPerpetuity(gridCharacter.perpetuity)
|
||||
}, [gridCharacter])
|
||||
|
||||
// Prepare the GridWeaponObject to send to the server
|
||||
function prepareObject() {
|
||||
let object: GridCharacterObject = {
|
||||
function prepareObject(): GridCharacterObject {
|
||||
return {
|
||||
character: {
|
||||
ring1: {
|
||||
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,
|
||||
},
|
||||
rings: rings, // your local rings array
|
||||
earring: {
|
||||
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,
|
||||
perpetuity: perpetuity,
|
||||
},
|
||||
}
|
||||
|
||||
if (awakening) {
|
||||
object.character.awakening_id = awakening.id
|
||||
object.character.awakening_level = awakeningLevel
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// Methods: Modification checking
|
||||
|
|
@ -152,12 +144,12 @@ const CharacterModal = ({
|
|||
|
||||
function ringsChanged() {
|
||||
// Create an empty ExtendedMastery object
|
||||
const emptyRingset: CharacterOverMastery = {
|
||||
1: { ...emptyExtendedMastery, modifier: 1 },
|
||||
2: { ...emptyExtendedMastery, modifier: 2 },
|
||||
3: emptyExtendedMastery,
|
||||
4: emptyExtendedMastery,
|
||||
}
|
||||
const emptyRingset: CharacterOverMastery = [
|
||||
{ ...emptyExtendedMastery, modifier: 1 },
|
||||
{ ...emptyExtendedMastery, modifier: 2 },
|
||||
emptyExtendedMastery,
|
||||
emptyExtendedMastery,
|
||||
]
|
||||
|
||||
// Check if the current ringset is empty on the current GridCharacter and our local state
|
||||
const isEmptyRingset =
|
||||
|
|
@ -195,8 +187,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.awakening?.type, awakening) ||
|
||||
gridCharacter.awakening?.level !== awakeningLevel
|
||||
|
||||
// Return true if the awakening has been modified and is not empty
|
||||
return awakeningChanged
|
||||
|
|
@ -227,8 +219,26 @@ const CharacterModal = ({
|
|||
})
|
||||
}
|
||||
|
||||
function receiveAwakeningValues(id: string, level: number) {
|
||||
setAwakening(gridCharacter.object.awakenings.find((a) => a.id === id))
|
||||
function receiveAwakeningValues(slug: string, level: number) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -307,13 +317,13 @@ const CharacterModal = ({
|
|||
object="earring"
|
||||
dataSet={elementalizeAetherialMastery(gridCharacter)}
|
||||
selectValue={
|
||||
gridCharacter.aetherial_mastery
|
||||
? gridCharacter.aetherial_mastery.modifier
|
||||
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
|
||||
? gridCharacter.aetherial_mastery?.modifier
|
||||
: 0
|
||||
}
|
||||
inputValue={
|
||||
gridCharacter.aetherial_mastery
|
||||
? gridCharacter.aetherial_mastery.strength
|
||||
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
|
||||
? gridCharacter.aetherial_mastery?.strength
|
||||
: 0
|
||||
}
|
||||
sendValidity={receiveValidity}
|
||||
|
|
|
|||
|
|
@ -148,12 +148,12 @@ const CharacterUnit = ({
|
|||
let character = cloneDeep(gridCharacter)
|
||||
|
||||
if (character.over_mastery) {
|
||||
const overMastery: CharacterOverMastery = {
|
||||
1: gridCharacter.over_mastery[0],
|
||||
2: gridCharacter.over_mastery[1],
|
||||
3: gridCharacter.over_mastery[2],
|
||||
4: gridCharacter.over_mastery[3],
|
||||
}
|
||||
const overMastery: CharacterOverMastery = [
|
||||
gridCharacter.over_mastery[0],
|
||||
gridCharacter.over_mastery[1],
|
||||
gridCharacter.over_mastery[2],
|
||||
gridCharacter.over_mastery[3],
|
||||
]
|
||||
|
||||
character.over_mastery = overMastery
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// Core dependencies
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
|
@ -73,7 +72,8 @@ const AwakeningSelectWithInput = ({
|
|||
setCurrentAwakening(awakening)
|
||||
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
|
||||
|
|
@ -90,35 +90,32 @@ const AwakeningSelectWithInput = ({
|
|||
|
||||
// Methods: Rendering
|
||||
function generateOptions() {
|
||||
const sortedDataSet = [...dataSet].sort((a, b) => {
|
||||
return a.order - b.order
|
||||
})
|
||||
|
||||
let options: React.ReactNode[] = sortedDataSet.map((awakening, i) => {
|
||||
return generateItem(awakening)
|
||||
})
|
||||
|
||||
const sortedDataSet = [...dataSet].sort((a, b) => a.order - b.order)
|
||||
let options: React.ReactNode[] = sortedDataSet.map((awakening) =>
|
||||
generateItem(awakening)
|
||||
)
|
||||
if (!dataSet.includes(defaultAwakening))
|
||||
options.unshift(generateItem(defaultAwakening))
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
function generateItem(awakening: Awakening) {
|
||||
return (
|
||||
<SelectItem key={awakening.slug} value={awakening.id}>
|
||||
<SelectItem key={awakening.slug} value={awakening.slug}>
|
||||
{awakening.name[locale]}
|
||||
</SelectItem>
|
||||
)
|
||||
}
|
||||
|
||||
// Methods: User input detection
|
||||
function handleSelectChange(id: string) {
|
||||
function handleSelectChange(value: string) {
|
||||
// Here, value is the awakening slug.
|
||||
const input = inputRef.current
|
||||
if (input && !handleInputError(parseFloat(input.value))) return
|
||||
|
||||
setCurrentAwakening(dataSet.find((awakening) => awakening.id === id))
|
||||
sendValues(id, currentLevel)
|
||||
const selectedAwakening = dataSet.find((a) => a.slug === value)
|
||||
setCurrentAwakening(selectedAwakening)
|
||||
sendValues(value, currentLevel)
|
||||
}
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
|
|
@ -127,7 +124,10 @@ const AwakeningSelectWithInput = ({
|
|||
|
||||
const newLevel = parseInt(event.target.value)
|
||||
setCurrentLevel(newLevel)
|
||||
sendValues(currentAwakening ? currentAwakening.id : '0', newLevel)
|
||||
sendValues(
|
||||
currentAwakening ? currentAwakening.slug : defaultAwakening.slug,
|
||||
newLevel
|
||||
)
|
||||
}
|
||||
|
||||
// Methods: Handle error
|
||||
|
|
@ -135,16 +135,12 @@ const AwakeningSelectWithInput = ({
|
|||
let error = ''
|
||||
|
||||
if (currentAwakening) {
|
||||
if (value && value % 1 != 0) {
|
||||
if (value && value % 1 !== 0) {
|
||||
error = t(`awakening.errors.value_not_whole`)
|
||||
} else if (value < 1) {
|
||||
error = t(`awakening.errors.value_too_low`, {
|
||||
minValue: 1,
|
||||
})
|
||||
error = t(`awakening.errors.value_too_low`, { minValue: 1 })
|
||||
} else if (value > maxLevel) {
|
||||
error = t(`awakening.errors.value_too_high`, {
|
||||
maxValue: maxLevel,
|
||||
})
|
||||
error = t(`awakening.errors.value_too_high`, { maxValue: maxLevel })
|
||||
} else if (!value || value <= 0) {
|
||||
error = t(`awakening.errors.value_empty`)
|
||||
} else {
|
||||
|
|
@ -165,13 +161,9 @@ const AwakeningSelectWithInput = ({
|
|||
|
||||
const rangeString = () => {
|
||||
let placeholder = ''
|
||||
|
||||
if (awakening) {
|
||||
const minValue = 1
|
||||
const maxValue = maxLevel
|
||||
placeholder = `${minValue}~${maxValue}`
|
||||
if (currentAwakening) {
|
||||
placeholder = `1~${maxLevel}`
|
||||
}
|
||||
|
||||
return placeholder
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +172,8 @@ const AwakeningSelectWithInput = ({
|
|||
<div className={styles.set}>
|
||||
<Select
|
||||
key="awakening-type"
|
||||
value={`${awakening ? awakening.id : defaultAwakening.id}`}
|
||||
// Use the slug as the value
|
||||
value={`${awakening ? awakening.slug : defaultAwakening.slug}`}
|
||||
open={open}
|
||||
disabled={selectDisabled}
|
||||
onValueChange={handleSelectChange}
|
||||
|
|
@ -200,7 +193,8 @@ const AwakeningSelectWithInput = ({
|
|||
className={inputClasses}
|
||||
fieldsetClassName={classNames({
|
||||
hidden:
|
||||
currentAwakening === undefined || currentAwakening.id === '0',
|
||||
currentAwakening === undefined ||
|
||||
currentAwakening.slug === defaultAwakening.slug,
|
||||
})}
|
||||
wrapperClassName="fullHeight"
|
||||
bound={true}
|
||||
|
|
|
|||
|
|
@ -25,21 +25,21 @@ interface Props {
|
|||
|
||||
const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
||||
// Ring value states
|
||||
const [rings, setRings] = useState<CharacterOverMastery>({
|
||||
1: { ...emptyRing, modifier: 1 },
|
||||
2: { ...emptyRing, modifier: 2 },
|
||||
3: emptyRing,
|
||||
4: emptyRing,
|
||||
})
|
||||
const [rings, setRings] = useState<CharacterOverMastery>([
|
||||
{ ...emptyRing, modifier: 1 },
|
||||
{ ...emptyRing, modifier: 2 },
|
||||
emptyRing,
|
||||
emptyRing,
|
||||
])
|
||||
|
||||
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],
|
||||
})
|
||||
setRings([
|
||||
gridCharacter.over_mastery[0],
|
||||
gridCharacter.over_mastery[1],
|
||||
gridCharacter.over_mastery[2],
|
||||
gridCharacter.over_mastery[3],
|
||||
])
|
||||
}
|
||||
}, [gridCharacter])
|
||||
|
||||
|
|
@ -64,13 +64,13 @@ const RingSelect = ({ gridCharacter, sendValues }: Props) => {
|
|||
}
|
||||
|
||||
switch (index) {
|
||||
case 1:
|
||||
case 0:
|
||||
return overMastery.a ? [overMastery.a[0]] : []
|
||||
case 2:
|
||||
case 1:
|
||||
return overMastery.a ? [overMastery.a[1]] : []
|
||||
case 3:
|
||||
case 2:
|
||||
return overMastery.b ? [noValue, ...overMastery.b] : []
|
||||
case 4:
|
||||
case 3:
|
||||
return overMastery.c ? [noValue, ...overMastery.c] : []
|
||||
default:
|
||||
return []
|
||||
|
|
@ -78,72 +78,74 @@ 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) {
|
||||
setSyncedRingValues(index, right)
|
||||
} else if (index == 3 && left == 0) {
|
||||
setRings({
|
||||
...rings,
|
||||
3: {
|
||||
modifier: 0,
|
||||
strength: 0,
|
||||
},
|
||||
4: {
|
||||
modifier: 0,
|
||||
strength: 0,
|
||||
},
|
||||
if (index === 0 || index === 1) {
|
||||
// For rings 1 and 2 (indices 0 and 1), update using the synced function.
|
||||
setSyncedRingValues(index as 0 | 1, right)
|
||||
} else if (index === 2 && left === 0) {
|
||||
// If ring 3 (index 2) is being unset (left is 0), then also unset ring 4.
|
||||
setRings((prev) => {
|
||||
const newRings = [...prev]
|
||||
newRings[2] = { modifier: 0, strength: 0 }
|
||||
newRings[3] = { modifier: 0, strength: 0 }
|
||||
return newRings
|
||||
})
|
||||
} else {
|
||||
setRings({
|
||||
...rings,
|
||||
[index]: {
|
||||
modifier: left,
|
||||
strength: right,
|
||||
},
|
||||
// For any other case (including ring 4 being unset), update only that ring.
|
||||
setRings((prev) => {
|
||||
const newRings = [...prev]
|
||||
newRings[index] = { modifier: left, strength: right }
|
||||
return newRings
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function setSyncedRingValues(index: 1 | 2, value: number) {
|
||||
// console.log(`Setting synced value for ${index} with value ${value}`)
|
||||
const atkValues = (dataSet(1)[0] as ItemSkill).values ?? []
|
||||
const hpValues = (dataSet(2)[0] as ItemSkill).values ?? []
|
||||
function setSyncedRingValues(changedIndex: 0 | 1, newStrength: number) {
|
||||
// Assume dataSet(0) holds the attack-related data and dataSet(1) holds the HP-related data.
|
||||
// (Adjust these calls if your datasets are in different positions.)
|
||||
const attackItem = dataSet(0)[0] as ItemSkill
|
||||
const hpItem = dataSet(1)[0] as ItemSkill
|
||||
|
||||
const found =
|
||||
index === 1 ? atkValues.indexOf(value) : hpValues.indexOf(value)
|
||||
const atkValue = atkValues[found] ?? 0
|
||||
const hpValue = hpValues[found] ?? 0
|
||||
const attackValues: number[] = attackItem.values ?? []
|
||||
const hpValues: number[] = hpItem.values ?? []
|
||||
|
||||
setRings({
|
||||
...rings,
|
||||
1: {
|
||||
modifier: 1,
|
||||
strength: atkValue,
|
||||
},
|
||||
2: {
|
||||
modifier: 2,
|
||||
strength: hpValue,
|
||||
},
|
||||
// Determine the index based on which ring changed:
|
||||
const selectedIndex =
|
||||
changedIndex === 0
|
||||
? attackValues.indexOf(newStrength)
|
||||
: hpValues.indexOf(newStrength)
|
||||
|
||||
// If the new strength value isn’t found, do nothing.
|
||||
if (selectedIndex === -1) {
|
||||
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 (
|
||||
<div className={styles.rings}>
|
||||
{[...Array(4)].map((e, i) => {
|
||||
const index = i + 1
|
||||
const ringStat = rings[index]
|
||||
|
||||
{rings.map((ringStat, i) => {
|
||||
return (
|
||||
<ExtendedMasterySelect
|
||||
name={`ring-${index}`}
|
||||
name={`ring-${i}`}
|
||||
object="ring"
|
||||
key={`ring-${index}`}
|
||||
dataSet={dataSet(index)}
|
||||
leftSelectDisabled={index === 1 || index === 2}
|
||||
leftSelectValue={ringStat.modifier ? ringStat.modifier : 0}
|
||||
rightSelectValue={ringStat.strength ? ringStat.strength : 0}
|
||||
key={`ring-${i}`}
|
||||
dataSet={dataSet(i)}
|
||||
leftSelectDisabled={i === 0 || i === 1}
|
||||
leftSelectValue={ringStat?.modifier ?? 0}
|
||||
rightSelectValue={ringStat?.strength ?? 0}
|
||||
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) => {
|
||||
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],
|
||||
}
|
||||
if (character.over_mastery && character.over_mastery) {
|
||||
const overMastery: CharacterOverMastery = [
|
||||
object.over_mastery[0],
|
||||
object.over_mastery[1],
|
||||
object.over_mastery[2],
|
||||
object.over_mastery[3],
|
||||
]
|
||||
|
||||
character.over_mastery = overMastery
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const PartyHead = ({ party, meta }: Props) => {
|
|||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
const previewUrl = `${
|
||||
process.env.NEXT_PUBLIC_SITE_URL || 'https://granblue.team'
|
||||
}/preview/${party.shortcode}`
|
||||
}/p/${party.shortcode}/preview`
|
||||
|
||||
return (
|
||||
<Head>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,67 @@
|
|||
// Overall container – never taller than $rep-height:
|
||||
.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;
|
||||
transition: $duration-opacity-fade opacity ease-in;
|
||||
opacity: 0.5;
|
||||
display: grid;
|
||||
// First column: mainhand width = $rep-height * (200/420)
|
||||
// Second column: weapons grid – its width will be auto (we calculate it below)
|
||||
grid-template-columns:
|
||||
calc(#{$rep-height} * (200 / 420))
|
||||
auto;
|
||||
gap: $unit-half;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@include breakpoint(small-tablet) {
|
||||
display: none;
|
||||
}
|
||||
/* --- Mainhand image --- */
|
||||
.mainhand {
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
width: 100%; // takes the grid column’s computed width
|
||||
overflow: hidden;
|
||||
|
||||
.mainhand,
|
||||
.weapon {
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
|
||||
img[src*='jpg'] {
|
||||
border-radius: 4px;
|
||||
width: 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;
|
||||
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 {
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
// Center the image (or placeholder) within the cell:
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,13 +136,12 @@ const WeaponModal = ({
|
|||
}
|
||||
|
||||
// Receive values from AwakeningSelectWithInput
|
||||
function receiveAwakeningValues(id: string, level: number) {
|
||||
setAwakening(gridWeapon.object.awakenings.find((a) => a.id === id))
|
||||
console.log(level)
|
||||
function receiveAwakeningValues(slug: string, level: number) {
|
||||
// Look up the awakening by its slug, since the select sends a slug.
|
||||
setAwakening(gridWeapon.object.awakenings.find((a) => a.slug === slug))
|
||||
setAwakeningLevel(level)
|
||||
setFormValid(true)
|
||||
}
|
||||
|
||||
// Receive values from WeaponKeySelect
|
||||
function receiveWeaponKey(value: WeaponKey, slot: number) {
|
||||
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
|
||||
uncap_level: number
|
||||
transcendence_step: number
|
||||
over_mastery: CharacterOverMastery
|
||||
aetherial_mastery: ExtendedMastery
|
||||
perpetuity: boolean
|
||||
over_mastery: ExtendedMastery[]
|
||||
aetherial_mastery?: ExtendedMastery
|
||||
awakening: {
|
||||
type: Awakening
|
||||
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
|
||||
}
|
||||
|
||||
export type CharacterOverMastery = {
|
||||
[key: number]: ExtendedMastery
|
||||
1: ExtendedMastery
|
||||
2: ExtendedMastery
|
||||
3: ExtendedMastery
|
||||
4: ExtendedMastery
|
||||
export type CharacterOverMastery = ExtendedMastery[]
|
||||
|
||||
export interface MasteryBonuses {
|
||||
awakening?: {
|
||||
type: Awakening
|
||||
level: number
|
||||
}
|
||||
over_mastery?: CharacterOverMastery
|
||||
aetherial_mastery?: ExtendedMastery
|
||||
}
|
||||
|
||||
interface GridCharacterObject {
|
||||
export interface GridCharacterObject {
|
||||
character: {
|
||||
ring1: ExtendedMastery
|
||||
ring2: ExtendedMastery
|
||||
ring3: ExtendedMastery
|
||||
ring4: ExtendedMastery
|
||||
rings: ExtendedMastery[]
|
||||
earring: ExtendedMastery
|
||||
awakening_id?: string
|
||||
awakening_level?: number
|
||||
awakening?: {
|
||||
id: string
|
||||
level: number
|
||||
}
|
||||
transcendence_step: number
|
||||
perpetuity: boolean
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue