fix: render related characters
This commit is contained in:
parent
d5a22baa0a
commit
5df563198b
3 changed files with 308 additions and 189 deletions
|
|
@ -73,6 +73,7 @@ export interface Weapon {
|
||||||
export interface Character {
|
export interface Character {
|
||||||
id: string
|
id: string
|
||||||
granblueId: string
|
granblueId: string
|
||||||
|
characterId?: number
|
||||||
name: {
|
name: {
|
||||||
en?: string
|
en?: string
|
||||||
ja?: string
|
ja?: string
|
||||||
|
|
@ -186,6 +187,16 @@ export class EntityAdapter extends BaseAdapter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets related characters (same character_id) for a given character
|
||||||
|
*/
|
||||||
|
async getRelatedCharacters(id: string): Promise<Character[]> {
|
||||||
|
return this.request<Character[]>(`/characters/${id}/related`, {
|
||||||
|
method: 'GET',
|
||||||
|
cacheTTL: 600000 // Cache for 10 minutes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets canonical summon data by ID
|
* Gets canonical summon data by ID
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -39,18 +39,20 @@
|
||||||
const granblueId = $derived(item?.granblue_id)
|
const granblueId = $derived(item?.granblue_id)
|
||||||
|
|
||||||
// Get element name for button styling
|
// Get element name for button styling
|
||||||
const elementName = $derived((() => {
|
const elementName = $derived(
|
||||||
const elementMap: Record<number, string | undefined> = {
|
(() => {
|
||||||
0: undefined, // Null element
|
const elementMap: Record<number, string | undefined> = {
|
||||||
1: 'wind',
|
0: undefined, // Null element
|
||||||
2: 'fire',
|
1: 'wind',
|
||||||
3: 'water',
|
2: 'fire',
|
||||||
4: 'earth',
|
3: 'water',
|
||||||
5: 'dark',
|
4: 'earth',
|
||||||
6: 'light'
|
5: 'dark',
|
||||||
}
|
6: 'light'
|
||||||
return elementMap[element] || undefined
|
}
|
||||||
})())
|
return elementMap[element] || undefined
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
|
||||||
// Helper function to get display name
|
// Helper function to get display name
|
||||||
function getDisplayName(nameObj: string | { en?: string; ja?: string }): string {
|
function getDisplayName(nameObj: string | { en?: string; ja?: string }): string {
|
||||||
|
|
@ -60,7 +62,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="container">
|
<header class="container">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img
|
<img
|
||||||
|
|
@ -109,7 +111,14 @@
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="medium"
|
size="medium"
|
||||||
element={elementName as "fire" | "water" | "earth" | "wind" | "light" | "dark" | undefined}
|
element={elementName as
|
||||||
|
| 'fire'
|
||||||
|
| 'water'
|
||||||
|
| 'earth'
|
||||||
|
| 'wind'
|
||||||
|
| 'light'
|
||||||
|
| 'dark'
|
||||||
|
| undefined}
|
||||||
onclick={onSave}
|
onclick={onSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
>
|
>
|
||||||
|
|
@ -120,7 +129,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</header>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '$src/themes/colors' as colors;
|
@use '$src/themes/colors' as colors;
|
||||||
|
|
@ -135,9 +144,9 @@
|
||||||
gap: spacing.$unit * 2;
|
gap: spacing.$unit * 2;
|
||||||
padding: spacing.$unit * 2;
|
padding: spacing.$unit * 2;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
position: sticky;
|
// position: sticky;
|
||||||
top: 0;
|
// top: 0;
|
||||||
z-index: 10;
|
// z-index: 10;
|
||||||
background: white;
|
background: white;
|
||||||
border-top-left-radius: layout.$card-corner;
|
border-top-left-radius: layout.$card-corner;
|
||||||
border-top-right-radius: layout.$card-corner;
|
border-top-right-radius: layout.$card-corner;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
// TanStack Query
|
// TanStack Query
|
||||||
import { createQuery } from '@tanstack/svelte-query'
|
import { createQuery } from '@tanstack/svelte-query'
|
||||||
import { entityQueries } from '$lib/api/queries/entity.queries'
|
import { entityQueries } from '$lib/api/queries/entity.queries'
|
||||||
|
import { entityAdapter } from '$lib/api/adapters/entity.adapter'
|
||||||
import { withInitialData } from '$lib/query/ssr'
|
import { withInitialData } from '$lib/query/ssr'
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
|
|
@ -43,6 +44,16 @@
|
||||||
|
|
||||||
// Edit mode state
|
// Edit mode state
|
||||||
let editMode = $state(false)
|
let editMode = $state(false)
|
||||||
|
|
||||||
|
// Query for related characters (same character_id)
|
||||||
|
const relatedQuery = createQuery(() => ({
|
||||||
|
queryKey: ['characters', 'related', character?.id],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!character?.id) return []
|
||||||
|
return entityAdapter.getRelatedCharacters(character.id)
|
||||||
|
},
|
||||||
|
enabled: !!character?.characterId && !editMode
|
||||||
|
}))
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let saveError = $state<string | null>(null)
|
let saveError = $state<string | null>(null)
|
||||||
let saveSuccess = $state(false)
|
let saveSuccess = $state(false)
|
||||||
|
|
@ -51,6 +62,7 @@
|
||||||
let editData = $state({
|
let editData = $state({
|
||||||
name: character?.name || '',
|
name: character?.name || '',
|
||||||
granblueId: character?.granblueId || '',
|
granblueId: character?.granblueId || '',
|
||||||
|
characterId: character?.characterId ?? (null as number | null),
|
||||||
rarity: character?.rarity || 1,
|
rarity: character?.rarity || 1,
|
||||||
element: character?.element || 0,
|
element: character?.element || 0,
|
||||||
race1: character?.race?.[0] ?? null,
|
race1: character?.race?.[0] ?? null,
|
||||||
|
|
@ -76,6 +88,7 @@
|
||||||
editData = {
|
editData = {
|
||||||
name: character.name || '',
|
name: character.name || '',
|
||||||
granblueId: character.granblueId || '',
|
granblueId: character.granblueId || '',
|
||||||
|
characterId: character.characterId ?? null,
|
||||||
rarity: character.rarity || 1,
|
rarity: character.rarity || 1,
|
||||||
element: character.element || 0,
|
element: character.element || 0,
|
||||||
race1: character.race?.[0] ?? null,
|
race1: character.race?.[0] ?? null,
|
||||||
|
|
@ -114,6 +127,7 @@
|
||||||
editData = {
|
editData = {
|
||||||
name: character.name || '',
|
name: character.name || '',
|
||||||
granblueId: character.granblueId || '',
|
granblueId: character.granblueId || '',
|
||||||
|
characterId: character.characterId ?? null,
|
||||||
rarity: character.rarity || 1,
|
rarity: character.rarity || 1,
|
||||||
element: character.element || 0,
|
element: character.element || 0,
|
||||||
race1: character.race?.[0] ?? null,
|
race1: character.race?.[0] ?? null,
|
||||||
|
|
@ -145,9 +159,10 @@
|
||||||
const payload = {
|
const payload = {
|
||||||
name: editData.name,
|
name: editData.name,
|
||||||
granblue_id: editData.granblueId,
|
granblue_id: editData.granblueId,
|
||||||
|
character_id: editData.characterId,
|
||||||
rarity: editData.rarity,
|
rarity: editData.rarity,
|
||||||
element: editData.element,
|
element: editData.element,
|
||||||
race: [editData.race1, editData.race2].filter(r => r !== null && r !== undefined),
|
race: [editData.race1, editData.race2].filter((r) => r !== null && r !== undefined),
|
||||||
gender: editData.gender,
|
gender: editData.gender,
|
||||||
proficiency: [editData.proficiency1, editData.proficiency2],
|
proficiency: [editData.proficiency1, editData.proficiency2],
|
||||||
hp: {
|
hp: {
|
||||||
|
|
@ -210,14 +225,18 @@
|
||||||
const transcendence = $derived(uncap.transcendence ?? false)
|
const transcendence = $derived(uncap.transcendence ?? false)
|
||||||
const special = $derived(editMode ? editData.special : (character?.special ?? false))
|
const special = $derived(editMode ? editData.special : (character?.special ?? false))
|
||||||
|
|
||||||
const uncapLevel = $derived(getCharacterMaxUncapLevel({ special, uncap: { flb, ulb, transcendence } }))
|
const uncapLevel = $derived(
|
||||||
|
getCharacterMaxUncapLevel({ special, uncap: { flb, ulb, transcendence } })
|
||||||
|
)
|
||||||
const transcendenceStage = $derived(transcendence ? 5 : 0)
|
const transcendenceStage = $derived(transcendence ? 5 : 0)
|
||||||
|
|
||||||
// Get element name for checkbox theming
|
// Get element name for checkbox theming
|
||||||
const elementName = $derived.by(() => {
|
const elementName = $derived.by(() => {
|
||||||
const el = editMode ? editData.element : character?.element
|
const el = editMode ? editData.element : character?.element
|
||||||
const label = getElementLabel(el)
|
const label = getElementLabel(el)
|
||||||
return label !== '—' && label !== 'Null' ? label.toLowerCase() as 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' : undefined
|
return label !== '—' && label !== 'Null'
|
||||||
|
? (label.toLowerCase() as 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light')
|
||||||
|
: undefined
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -248,179 +267,223 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<DetailsContainer title="Metadata">
|
<section class="details">
|
||||||
{#if editMode}
|
<DetailsContainer title="Metadata">
|
||||||
<DetailItem
|
{#if editMode}
|
||||||
label="Rarity"
|
<DetailItem
|
||||||
bind:value={editData.rarity}
|
label="Rarity"
|
||||||
editable={true}
|
bind:value={editData.rarity}
|
||||||
type="select"
|
editable={true}
|
||||||
options={rarityOptions}
|
type="select"
|
||||||
/>
|
options={rarityOptions}
|
||||||
<DetailItem
|
|
||||||
label="Granblue ID"
|
|
||||||
bind:value={editData.granblueId}
|
|
||||||
editable={true}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<DetailItem label="Rarity" value={getRarityLabel(character.rarity)} />
|
|
||||||
<DetailItem label="Granblue ID" value={character.granblueId} />
|
|
||||||
{/if}
|
|
||||||
</DetailsContainer>
|
|
||||||
<DetailsContainer title="Details">
|
|
||||||
{#if character.uncap}
|
|
||||||
<DetailItem label="Uncap">
|
|
||||||
<UncapIndicator
|
|
||||||
type="character"
|
|
||||||
{uncapLevel}
|
|
||||||
{transcendenceStage}
|
|
||||||
{flb}
|
|
||||||
{ulb}
|
|
||||||
{transcendence}
|
|
||||||
{special}
|
|
||||||
editable={false}
|
|
||||||
/>
|
/>
|
||||||
</DetailItem>
|
<DetailItem
|
||||||
{/if}
|
label="Granblue ID"
|
||||||
|
bind:value={editData.granblueId}
|
||||||
{#if editMode}
|
editable={true}
|
||||||
<DetailItem label="FLB" bind:value={editData.flb} editable={true} type="checkbox" element={elementName} />
|
type="text"
|
||||||
<DetailItem label="ULB" bind:value={editData.ulb} editable={true} type="checkbox" element={elementName} />
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label="Transcendence"
|
label="Character ID"
|
||||||
bind:value={editData.transcendence}
|
bind:value={editData.characterId}
|
||||||
editable={true}
|
editable={true}
|
||||||
type="checkbox"
|
type="number"
|
||||||
element={elementName}
|
/>
|
||||||
/>
|
{:else}
|
||||||
<DetailItem
|
<DetailItem label="Rarity" value={getRarityLabel(character.rarity)} />
|
||||||
label="Special"
|
<DetailItem label="Granblue ID" value={character.granblueId} />
|
||||||
bind:value={editData.special}
|
|
||||||
editable={true}
|
|
||||||
type="checkbox"
|
|
||||||
element={elementName}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if editMode}
|
|
||||||
<DetailItem
|
|
||||||
label="Element"
|
|
||||||
bind:value={editData.element}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={elementOptions}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Race 1"
|
|
||||||
bind:value={editData.race1}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={raceOptions}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Race 2"
|
|
||||||
bind:value={editData.race2}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={raceOptions}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Gender"
|
|
||||||
bind:value={editData.gender}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={genderOptions}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Proficiency 1"
|
|
||||||
bind:value={editData.proficiency1}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={proficiencyOptions}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Proficiency 2"
|
|
||||||
bind:value={editData.proficiency2}
|
|
||||||
editable={true}
|
|
||||||
type="select"
|
|
||||||
options={proficiencyOptions}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<DetailItem label="Element" value={getElementLabel(character.element)} />
|
|
||||||
<DetailItem label="Race 1" value={getRaceLabel(character.race?.[0])} />
|
|
||||||
{#if character.race?.[1]}
|
|
||||||
<DetailItem label="Race 2" value={getRaceLabel(character.race?.[1])} />
|
|
||||||
{/if}
|
{/if}
|
||||||
<DetailItem label="Gender" value={getGenderLabel(character.gender)} />
|
</DetailsContainer>
|
||||||
<DetailItem label="Proficiency 1" value={getProficiencyLabel(character.proficiency?.[0] ?? 0)} />
|
|
||||||
<DetailItem label="Proficiency 2" value={getProficiencyLabel(character.proficiency?.[1] ?? 0)} />
|
|
||||||
{/if}
|
|
||||||
</DetailsContainer>
|
|
||||||
|
|
||||||
<DetailsContainer title="HP Stats">
|
<DetailsContainer title="Details">
|
||||||
{#if editMode}
|
{#if character.uncap}
|
||||||
<DetailItem
|
<DetailItem label="Uncap">
|
||||||
label="Base HP"
|
<UncapIndicator
|
||||||
bind:value={editData.minHp}
|
type="character"
|
||||||
editable={true}
|
{uncapLevel}
|
||||||
type="number"
|
{transcendenceStage}
|
||||||
placeholder="0"
|
{flb}
|
||||||
/>
|
{ulb}
|
||||||
<DetailItem
|
{transcendence}
|
||||||
label="Max HP"
|
{special}
|
||||||
bind:value={editData.maxHp}
|
editable={false}
|
||||||
editable={true}
|
/>
|
||||||
type="number"
|
</DetailItem>
|
||||||
placeholder="0"
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Max HP (FLB)"
|
|
||||||
bind:value={editData.maxHpFlb}
|
|
||||||
editable={true}
|
|
||||||
type="number"
|
|
||||||
placeholder="0"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<DetailItem label="Base HP" value={character.hp?.minHp} />
|
|
||||||
<DetailItem label="Max HP" value={character.hp?.maxHp} />
|
|
||||||
{#if flb}
|
|
||||||
<DetailItem label="Max HP (FLB)" value={character.hp?.maxHpFlb} />
|
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</DetailsContainer>
|
|
||||||
|
|
||||||
<DetailsContainer title="Attack Stats">
|
{#if editMode}
|
||||||
{#if editMode}
|
<DetailItem
|
||||||
<DetailItem
|
label="FLB"
|
||||||
label="Base Attack"
|
bind:value={editData.flb}
|
||||||
bind:value={editData.minAtk}
|
editable={true}
|
||||||
editable={true}
|
type="checkbox"
|
||||||
type="number"
|
element={elementName}
|
||||||
placeholder="0"
|
/>
|
||||||
/>
|
<DetailItem
|
||||||
<DetailItem
|
label="ULB"
|
||||||
label="Max Attack"
|
bind:value={editData.ulb}
|
||||||
bind:value={editData.maxAtk}
|
editable={true}
|
||||||
editable={true}
|
type="checkbox"
|
||||||
type="number"
|
element={elementName}
|
||||||
placeholder="0"
|
/>
|
||||||
/>
|
<DetailItem
|
||||||
<DetailItem
|
label="Transcendence"
|
||||||
label="Max Attack (FLB)"
|
bind:value={editData.transcendence}
|
||||||
bind:value={editData.maxAtkFlb}
|
editable={true}
|
||||||
editable={true}
|
type="checkbox"
|
||||||
type="number"
|
element={elementName}
|
||||||
placeholder="0"
|
/>
|
||||||
/>
|
<DetailItem
|
||||||
{:else}
|
label="Special"
|
||||||
<DetailItem label="Base Attack" value={character.atk?.minAtk} />
|
bind:value={editData.special}
|
||||||
<DetailItem label="Max Attack" value={character.atk?.maxAtk} />
|
editable={true}
|
||||||
{#if flb}
|
type="checkbox"
|
||||||
<DetailItem label="Max Attack (FLB)" value={character.atk?.maxAtkFlb} />
|
element={elementName}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if editMode}
|
||||||
|
<DetailItem
|
||||||
|
label="Element"
|
||||||
|
bind:value={editData.element}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={elementOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Race 1"
|
||||||
|
bind:value={editData.race1}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={raceOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Race 2"
|
||||||
|
bind:value={editData.race2}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={raceOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Gender"
|
||||||
|
bind:value={editData.gender}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={genderOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 1"
|
||||||
|
bind:value={editData.proficiency1}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={proficiencyOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 2"
|
||||||
|
bind:value={editData.proficiency2}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={proficiencyOptions}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Element" value={getElementLabel(character.element)} />
|
||||||
|
<DetailItem label="Race 1" value={getRaceLabel(character.race?.[0])} />
|
||||||
|
{#if character.race?.[1]}
|
||||||
|
<DetailItem label="Race 2" value={getRaceLabel(character.race?.[1])} />
|
||||||
|
{/if}
|
||||||
|
<DetailItem label="Gender" value={getGenderLabel(character.gender)} />
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 1"
|
||||||
|
value={getProficiencyLabel(character.proficiency?.[0] ?? 0)}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 2"
|
||||||
|
value={getProficiencyLabel(character.proficiency?.[1] ?? 0)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</DetailsContainer>
|
||||||
|
|
||||||
|
<DetailsContainer title="HP Stats">
|
||||||
|
{#if editMode}
|
||||||
|
<DetailItem
|
||||||
|
label="Base HP"
|
||||||
|
bind:value={editData.minHp}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max HP"
|
||||||
|
bind:value={editData.maxHp}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max HP (FLB)"
|
||||||
|
bind:value={editData.maxHpFlb}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Base HP" value={character.hp?.minHp} />
|
||||||
|
<DetailItem label="Max HP" value={character.hp?.maxHp} />
|
||||||
|
{#if flb}
|
||||||
|
<DetailItem label="Max HP (FLB)" value={character.hp?.maxHpFlb} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</DetailsContainer>
|
||||||
|
|
||||||
|
<DetailsContainer title="Attack Stats">
|
||||||
|
{#if editMode}
|
||||||
|
<DetailItem
|
||||||
|
label="Base Attack"
|
||||||
|
bind:value={editData.minAtk}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max Attack"
|
||||||
|
bind:value={editData.maxAtk}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max Attack (FLB)"
|
||||||
|
bind:value={editData.maxAtkFlb}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Base Attack" value={character.atk?.minAtk} />
|
||||||
|
<DetailItem label="Max Attack" value={character.atk?.maxAtk} />
|
||||||
|
{#if flb}
|
||||||
|
<DetailItem label="Max Attack (FLB)" value={character.atk?.maxAtkFlb} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</DetailsContainer>
|
||||||
|
|
||||||
|
{#if !editMode && relatedQuery.data?.length}
|
||||||
|
<DetailsContainer title="Related Units">
|
||||||
|
<div class="related-units">
|
||||||
|
{#each relatedQuery.data as related}
|
||||||
|
<a href="/database/characters/{related.id}" class="related-unit">
|
||||||
|
<img
|
||||||
|
src={getCharacterImage(related.granblueId, 'grid', '01')}
|
||||||
|
alt={related.name.en}
|
||||||
|
class="related-image"
|
||||||
|
/>
|
||||||
|
<span class="related-name">{related.name.en}</span>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</DetailsContainer>
|
||||||
{/if}
|
{/if}
|
||||||
</DetailsContainer>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="not-found">
|
<div class="not-found">
|
||||||
|
|
@ -461,9 +524,13 @@
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: layout.$card-corner;
|
border-radius: layout.$card-corner;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
overflow: visible; // Changed from hidden to allow sticky header
|
overflow: visible;
|
||||||
margin-top: spacing.$unit-2x;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-controls {
|
.edit-controls {
|
||||||
|
|
@ -494,4 +561,36 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.related-units {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: spacing.$unit-2x;
|
||||||
|
padding: spacing.$unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-unit {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: colors.$grey-30;
|
||||||
|
|
||||||
|
&:hover .related-image {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-image {
|
||||||
|
width: 128px;
|
||||||
|
height: auto;
|
||||||
|
border-radius: layout.$item-corner;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-name {
|
||||||
|
margin-top: spacing.$unit;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue