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:
Justin Edmund 2025-02-09 22:54:15 -08:00 committed by GitHub
parent eff96e5a37
commit a02a6c70aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 293 additions and 232 deletions

5
.aidigestignore Normal file
View file

@ -0,0 +1,5 @@
public/images
public/labels
public/profiles
tsconfig.tsbuildinfo
*.log

1
.gitignore vendored
View file

@ -86,3 +86,4 @@ typings/
# DS_Store # DS_Store
.DS_Store .DS_Store
*.tsbuildinfo *.tsbuildinfo
codebase.md

View file

@ -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 = () => {
const gridAwakening = props.gridCharacter.awakening if (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}>

View file

@ -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 = ({
}) })
} }
setAwakening(gridCharacter.awakening.type) if (gridCharacter.awakening) {
setAwakeningLevel(gridCharacter.awakening.level) setAwakening(gridCharacter.awakening.type)
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}

View file

@ -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
} }

View file

@ -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}

View file

@ -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 isnt 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)
}} }}
/> />
) )

View file

@ -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
} }

View file

@ -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>

View file

@ -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)
grid-template-columns:
calc(#{$rep-height} * (200 / 420))
auto;
gap: $unit-half;
box-sizing: border-box;
}
@include breakpoint(small-tablet) { /* --- Mainhand image --- */
display: none; .mainhand {
} background: var(--card-bg);
border-radius: 4px;
height: 100%;
width: 100%; // takes the grid columns computed width
overflow: hidden;
.mainhand, img {
.weapon { width: 100%;
background: var(--card-bg); height: 100%;
border-radius: 4px; object-fit: contain; // or "cover" if you prefer cropping
}
img[src*='jpg'] { }
border-radius: 4px;
width: 100%; /* --- Weapons grid --- */
} .weapons {
} /* Reset default UL spacing */
margin: 0;
.mainhand { padding: 0;
aspect-ratio: 73/153; list-style: none;
display: grid; height: 100%;
grid-column: 1 / 2; /* spans one column */
} display: grid;
// We know there will be 3 columns and 3 rows.
.weapons { // Each row's height is one-third of $rep-height:
display: grid; /* make the right-images container a grid */ // Subtract the 2 vertical gaps from the total height before dividing:
grid-template-columns: repeat( grid-template-rows: repeat(
3, 3,
1fr calc((#{$rep-height} - (2 * #{$unit-half})) / 3)
); /* create 3 columns, each taking up 1 fraction */ );
grid-template-rows: repeat( // Each column's width is calculated as: (cell height * (280/160))
3, grid-template-columns: repeat(
1fr 3,
); /* create 3 rows, each taking up 1 fraction */ calc((#{$rep-height} - (2 * #{$unit-half})) / 3 * (280 / 160))
gap: $unit-half; );
// column-gap: $unit; gap: $unit-half;
// row-gap: $unit-2x; }
}
/* Each grid cell (a .weapon) */
.weapon { .weapon {
aspect-ratio: 280 / 160; background: var(--card-bg);
display: grid; 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%;
} }
} }

View file

@ -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)

View 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' })
}
}

View file

@ -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
View file

@ -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
} }