reconcile character detail and edit pages
- same section order on both pages - show all fields (empty shows dash) - add editable nicknames/links to edit page - handle CharacterSeriesRef[] -> number[] conversion
This commit is contained in:
parent
32c4880180
commit
9243d133cd
2 changed files with 222 additions and 75 deletions
|
|
@ -21,7 +21,6 @@
|
|||
import CharacterMetadataSection from '$lib/features/database/characters/sections/CharacterMetadataSection.svelte'
|
||||
import CharacterUncapSection from '$lib/features/database/characters/sections/CharacterUncapSection.svelte'
|
||||
import CharacterTaxonomySection from '$lib/features/database/characters/sections/CharacterTaxonomySection.svelte'
|
||||
import CharacterGachaSection from '$lib/features/database/characters/sections/CharacterGachaSection.svelte'
|
||||
import CharacterStatsSection from '$lib/features/database/characters/sections/CharacterStatsSection.svelte'
|
||||
import EntityImagesTab from '$lib/features/database/detail/tabs/EntityImagesTab.svelte'
|
||||
import EntityRawDataTab from '$lib/features/database/detail/tabs/EntityRawDataTab.svelte'
|
||||
|
|
@ -180,81 +179,83 @@
|
|||
{#if currentTab === 'info'}
|
||||
<section class="details">
|
||||
<CharacterMetadataSection {character} />
|
||||
|
||||
{#if character.nicknames?.en?.length || character.nicknames?.ja?.length}
|
||||
<DetailsContainer title="Nicknames">
|
||||
{#if character.nicknames?.en?.length}
|
||||
<DetailItem label="English">
|
||||
<div class="nickname-tags">
|
||||
{#each character.nicknames.en as nickname}
|
||||
<span class="nickname-tag">{nickname}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</DetailItem>
|
||||
{/if}
|
||||
{#if character.nicknames?.ja?.length}
|
||||
<DetailItem label="Japanese">
|
||||
<div class="nickname-tags">
|
||||
{#each character.nicknames.ja as nickname}
|
||||
<span class="nickname-tag">{nickname}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</DetailItem>
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
{/if}
|
||||
|
||||
<CharacterUncapSection {character} />
|
||||
<CharacterTaxonomySection {character} />
|
||||
<CharacterGachaSection {character} />
|
||||
<CharacterStatsSection {character} />
|
||||
|
||||
{#if character.releaseDate || character.flbDate || character.ulbDate}
|
||||
<DetailsContainer title="Dates">
|
||||
{#if character.releaseDate}
|
||||
<DetailItem label="Release Date" value={character.releaseDate} />
|
||||
<DetailsContainer title="Nicknames">
|
||||
<DetailItem label="Nicknames (EN)">
|
||||
{#if character.nicknames?.en?.length}
|
||||
<div class="nickname-tags">
|
||||
{#each character.nicknames.en as nickname}
|
||||
<span class="nickname-tag">{nickname}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
{#if character.flbDate}
|
||||
<DetailItem label="FLB Date" value={character.flbDate} />
|
||||
</DetailItem>
|
||||
<DetailItem label="Nicknames (JP)">
|
||||
{#if character.nicknames?.ja?.length}
|
||||
<div class="nickname-tags">
|
||||
{#each character.nicknames.ja as nickname}
|
||||
<span class="nickname-tag">{nickname}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
{#if character.ulbDate}
|
||||
<DetailItem label="ULB Date" value={character.ulbDate} />
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
{/if}
|
||||
</DetailItem>
|
||||
</DetailsContainer>
|
||||
|
||||
{#if character.links?.wikiEn || character.links?.wikiJa || character.links?.gamewith || character.links?.kamigame}
|
||||
<DetailsContainer title="Links">
|
||||
{#if character.links?.wikiEn}
|
||||
<DetailItem label="Wiki (EN)">
|
||||
<a href={character.links.wikiEn} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.links.wikiEn}
|
||||
</a>
|
||||
</DetailItem>
|
||||
<DetailsContainer title="Dates">
|
||||
<DetailItem label="Release Date" value={character.releaseDate || '—'} />
|
||||
{#if character.uncap?.flb}
|
||||
<DetailItem label="FLB Date" value={character.flbDate || '—'} />
|
||||
{/if}
|
||||
{#if character.uncap?.ulb}
|
||||
<DetailItem label="ULB Date" value={character.ulbDate || '—'} />
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Links">
|
||||
<DetailItem label="Wiki (EN)">
|
||||
{#if character.wiki?.en}
|
||||
<a href={character.wiki.en} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.wiki.en}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
{#if character.links?.wikiJa}
|
||||
<DetailItem label="Wiki (JP)">
|
||||
<a href={character.links.wikiJa} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.links.wikiJa}
|
||||
</a>
|
||||
</DetailItem>
|
||||
</DetailItem>
|
||||
<DetailItem label="Wiki (JP)">
|
||||
{#if character.wiki?.ja}
|
||||
<a href={character.wiki.ja} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.wiki.ja}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
{#if character.links?.gamewith}
|
||||
<DetailItem label="Gamewith">
|
||||
<a href={character.links.gamewith} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.links.gamewith}
|
||||
</a>
|
||||
</DetailItem>
|
||||
</DetailItem>
|
||||
<DetailItem label="Gamewith">
|
||||
{#if character.gamewith}
|
||||
<a href={character.gamewith} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.gamewith}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
{#if character.links?.kamigame}
|
||||
<DetailItem label="Kamigame">
|
||||
<a href={character.links.kamigame} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.links.kamigame}
|
||||
</a>
|
||||
</DetailItem>
|
||||
</DetailItem>
|
||||
<DetailItem label="Kamigame">
|
||||
{#if character.kamigame}
|
||||
<a href={character.kamigame} target="_blank" rel="noopener noreferrer" class="external-link">
|
||||
{character.kamigame}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="empty-value">—</span>
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
{/if}
|
||||
</DetailItem>
|
||||
</DetailsContainer>
|
||||
|
||||
{#if relatedQuery.data?.length}
|
||||
<DetailsContainer title="Related Units">
|
||||
|
|
@ -402,4 +403,8 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-value {
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -19,15 +19,21 @@
|
|||
import CharacterMetadataSection from '$lib/features/database/characters/sections/CharacterMetadataSection.svelte'
|
||||
import CharacterUncapSection from '$lib/features/database/characters/sections/CharacterUncapSection.svelte'
|
||||
import CharacterTaxonomySection from '$lib/features/database/characters/sections/CharacterTaxonomySection.svelte'
|
||||
import CharacterGachaSection from '$lib/features/database/characters/sections/CharacterGachaSection.svelte'
|
||||
import CharacterStatsSection from '$lib/features/database/characters/sections/CharacterStatsSection.svelte'
|
||||
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
|
||||
import DetailItem from '$lib/components/ui/DetailItem.svelte'
|
||||
import TagInput from '$lib/components/ui/TagInput.svelte'
|
||||
import { getCharacterImage } from '$lib/utils/images'
|
||||
import { CHARACTER_SERIES_NAMES } from '$lib/types/enums'
|
||||
|
||||
// Types
|
||||
import type { PageData } from './$types'
|
||||
|
||||
// Create reverse mapping from series name to integer
|
||||
const SERIES_NAME_TO_INT: Record<string, number> = Object.fromEntries(
|
||||
Object.entries(CHARACTER_SERIES_NAMES).map(([key, name]) => [name, Number(key)])
|
||||
)
|
||||
|
||||
let { data }: { data: PageData } = $props()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
|
@ -52,7 +58,7 @@
|
|||
let editData = $state({
|
||||
name: '',
|
||||
granblueId: '',
|
||||
characterId: null as number | null,
|
||||
characterId: '', // Comma-separated string for dual/trio units
|
||||
rarity: 1,
|
||||
element: 0,
|
||||
race1: null as number | null,
|
||||
|
|
@ -60,28 +66,68 @@
|
|||
gender: 0,
|
||||
proficiency1: 0,
|
||||
proficiency2: 0,
|
||||
season: 0,
|
||||
series: [] as number[],
|
||||
// HP stats
|
||||
minHp: 0,
|
||||
maxHp: 0,
|
||||
maxHpFlb: 0,
|
||||
maxHpUlb: 0,
|
||||
// Attack stats
|
||||
minAtk: 0,
|
||||
maxAtk: 0,
|
||||
maxAtkFlb: 0,
|
||||
maxAtkUlb: 0,
|
||||
// Other stats
|
||||
baseDa: 0,
|
||||
baseTa: 0,
|
||||
ougiRatio: 0,
|
||||
ougiRatioFlb: 0,
|
||||
// Uncap flags
|
||||
flb: false,
|
||||
ulb: false,
|
||||
transcendence: false,
|
||||
special: false,
|
||||
// Dates
|
||||
releaseDate: '',
|
||||
flbDate: '',
|
||||
ulbDate: ''
|
||||
ulbDate: '',
|
||||
// Nicknames
|
||||
nicknamesEn: [] as string[],
|
||||
nicknamesJp: [] as string[],
|
||||
// Links
|
||||
wikiEn: '',
|
||||
wikiJa: '',
|
||||
gamewith: '',
|
||||
kamigame: ''
|
||||
})
|
||||
|
||||
// Helper to convert series to number array (handles both legacy integer and object formats)
|
||||
function seriesAsNumbers(
|
||||
series: number[] | Array<{ id: string; name?: { en?: string } }> | undefined
|
||||
): number[] {
|
||||
if (!series || series.length === 0) return []
|
||||
// Check if first element is an object (CharacterSeriesRef) or number
|
||||
const first = series[0]
|
||||
if (typeof first === 'object' && first !== null && 'id' in first) {
|
||||
// It's CharacterSeriesRef[] - convert using name.en to look up enum value
|
||||
return (series as Array<{ id: string; name?: { en?: string } }>)
|
||||
.map((s) => {
|
||||
const name = s.name?.en
|
||||
return name ? SERIES_NAME_TO_INT[name] : undefined
|
||||
})
|
||||
.filter((n): n is number => n !== undefined)
|
||||
}
|
||||
return series as number[]
|
||||
}
|
||||
|
||||
// Populate edit data when character loads
|
||||
$effect(() => {
|
||||
if (character) {
|
||||
editData = {
|
||||
name: character.name?.en || '',
|
||||
granblueId: character.granblueId || '',
|
||||
characterId: character.characterId ?? null,
|
||||
characterId: character.characterId?.join(', ') || '',
|
||||
rarity: character.rarity || 1,
|
||||
element: character.element || 0,
|
||||
race1: character.race?.[0] ?? null,
|
||||
|
|
@ -89,19 +135,40 @@
|
|||
gender: character.gender || 0,
|
||||
proficiency1: character.proficiency?.[0] || 0,
|
||||
proficiency2: character.proficiency?.[1] || 0,
|
||||
season: character.season || 0,
|
||||
series: seriesAsNumbers(character.series),
|
||||
// HP stats
|
||||
minHp: character.hp?.minHp || 0,
|
||||
maxHp: character.hp?.maxHp || 0,
|
||||
maxHpFlb: character.hp?.maxHpFlb || 0,
|
||||
maxHpUlb: character.hp?.maxHpUlb || 0,
|
||||
// Attack stats
|
||||
minAtk: character.atk?.minAtk || 0,
|
||||
maxAtk: character.atk?.maxAtk || 0,
|
||||
maxAtkFlb: character.atk?.maxAtkFlb || 0,
|
||||
maxAtkUlb: character.atk?.maxAtkUlb || 0,
|
||||
// Other stats
|
||||
baseDa: character.baseDa || 0,
|
||||
baseTa: character.baseTa || 0,
|
||||
ougiRatio: character.ougiRatio?.ougiRatio || 0,
|
||||
ougiRatioFlb: character.ougiRatio?.ougiRatioFlb || 0,
|
||||
// Uncap flags
|
||||
flb: character.uncap?.flb || false,
|
||||
ulb: character.uncap?.ulb || false,
|
||||
transcendence: character.uncap?.transcendence || false,
|
||||
special: character.special || false,
|
||||
// Dates
|
||||
releaseDate: character.releaseDate || '',
|
||||
flbDate: character.flbDate || '',
|
||||
ulbDate: character.ulbDate || ''
|
||||
ulbDate: character.ulbDate || '',
|
||||
// Nicknames
|
||||
nicknamesEn: character.nicknames?.en || [],
|
||||
nicknamesJp: character.nicknames?.ja || [],
|
||||
// Links
|
||||
wikiEn: character.wiki?.en || '',
|
||||
wikiJa: character.wiki?.ja || '',
|
||||
gamewith: character.gamewith || '',
|
||||
kamigame: character.kamigame || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -117,7 +184,13 @@
|
|||
const payload = {
|
||||
name_en: editData.name,
|
||||
granblue_id: editData.granblueId,
|
||||
character_id: editData.characterId ? [editData.characterId] : [],
|
||||
character_id:
|
||||
editData.characterId.trim() === ''
|
||||
? []
|
||||
: editData.characterId
|
||||
.split(',')
|
||||
.map((id) => Number(id.trim()))
|
||||
.filter((id) => !isNaN(id)),
|
||||
rarity: editData.rarity,
|
||||
element: editData.element,
|
||||
race1: editData.race1,
|
||||
|
|
@ -125,18 +198,40 @@
|
|||
gender: editData.gender,
|
||||
proficiency1: editData.proficiency1,
|
||||
proficiency2: editData.proficiency2,
|
||||
season: editData.season || undefined,
|
||||
series: editData.series,
|
||||
// HP stats
|
||||
min_hp: editData.minHp,
|
||||
max_hp: editData.maxHp,
|
||||
max_hp_flb: editData.maxHpFlb,
|
||||
max_hp_ulb: editData.maxHpUlb,
|
||||
// Attack stats
|
||||
min_atk: editData.minAtk,
|
||||
max_atk: editData.maxAtk,
|
||||
max_atk_flb: editData.maxAtkFlb,
|
||||
max_atk_ulb: editData.maxAtkUlb,
|
||||
// Other stats
|
||||
base_da: editData.baseDa,
|
||||
base_ta: editData.baseTa,
|
||||
ougi_ratio: editData.ougiRatio,
|
||||
ougi_ratio_flb: editData.ougiRatioFlb,
|
||||
// Uncap flags
|
||||
flb: editData.flb,
|
||||
ulb: editData.ulb,
|
||||
transcendence: editData.transcendence,
|
||||
special: editData.special,
|
||||
release_date: editData.releaseDate || null,
|
||||
flb_date: editData.flbDate || null,
|
||||
ulb_date: editData.ulbDate || null
|
||||
// Dates
|
||||
release_date: editData.releaseDate || undefined,
|
||||
flb_date: editData.flbDate || undefined,
|
||||
ulb_date: editData.ulbDate || undefined,
|
||||
// Nicknames
|
||||
nicknames_en: editData.nicknamesEn,
|
||||
nicknames_jp: editData.nicknamesJp,
|
||||
// Links
|
||||
wiki_en: editData.wikiEn || undefined,
|
||||
wiki_ja: editData.wikiJa || undefined,
|
||||
gamewith: editData.gamewith || undefined,
|
||||
kamigame: editData.kamigame || undefined
|
||||
}
|
||||
|
||||
await entityAdapter.updateCharacter(character.id, payload)
|
||||
|
|
@ -186,9 +281,21 @@
|
|||
<CharacterMetadataSection {character} {editMode} bind:editData />
|
||||
<CharacterUncapSection {character} {editMode} bind:editData />
|
||||
<CharacterTaxonomySection {character} {editMode} bind:editData />
|
||||
<CharacterGachaSection {character} {editMode} bind:editData />
|
||||
<CharacterStatsSection {character} {editMode} bind:editData />
|
||||
|
||||
<DetailsContainer title="Nicknames">
|
||||
<DetailItem label="Nicknames (EN)">
|
||||
<TagInput bind:value={editData.nicknamesEn} placeholder="Add nickname..." contained />
|
||||
</DetailItem>
|
||||
<DetailItem label="Nicknames (JP)">
|
||||
<TagInput
|
||||
bind:value={editData.nicknamesJp}
|
||||
placeholder="ニックネームを入力"
|
||||
contained
|
||||
/>
|
||||
</DetailItem>
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Dates">
|
||||
<DetailItem
|
||||
label="Release Date"
|
||||
|
|
@ -213,6 +320,41 @@
|
|||
/>
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Links">
|
||||
<DetailItem
|
||||
label="Wiki (EN)"
|
||||
bind:value={editData.wikiEn}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="https://gbf.wiki/..."
|
||||
width="480px"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Wiki (JP)"
|
||||
bind:value={editData.wikiJa}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="https://gbf-wiki.com/..."
|
||||
width="480px"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Gamewith"
|
||||
bind:value={editData.gamewith}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="https://xn--bck3aza1a2if6kra4ee0hf.gamewith.jp/..."
|
||||
width="480px"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Kamigame"
|
||||
bind:value={editData.kamigame}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="https://kamigame.jp/..."
|
||||
width="480px"
|
||||
/>
|
||||
</DetailsContainer>
|
||||
</section>
|
||||
</DetailScaffold>
|
||||
{:else}
|
||||
|
|
|
|||
Loading…
Reference in a new issue