show dates on entity detail pages

This commit is contained in:
Justin Edmund 2025-12-02 01:24:53 -08:00
parent 6f1f0c60a2
commit 1b8232ed5a
3 changed files with 270 additions and 27 deletions

View file

@ -17,10 +17,10 @@
import CharacterUncapSection from '$lib/features/database/characters/sections/CharacterUncapSection.svelte'
import CharacterTaxonomySection from '$lib/features/database/characters/sections/CharacterTaxonomySection.svelte'
import CharacterStatsSection from '$lib/features/database/characters/sections/CharacterStatsSection.svelte'
import CharacterImagesSection from '$lib/features/database/characters/sections/CharacterImagesSection.svelte'
import EntityImagesTab from '$lib/features/database/detail/tabs/EntityImagesTab.svelte'
import EntityRawDataTab from '$lib/features/database/detail/tabs/EntityRawDataTab.svelte'
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import { getCharacterImage } from '$lib/utils/images'
// Types
@ -81,6 +81,9 @@
return getCharacterImage(character?.granblueId, 'grid', '01')
}
// Available image sizes for characters
const characterSizes = ['detail', 'grid', 'main', 'square']
// Generate image items for character (variants and poses based on uncap level)
const characterImages = $derived.by((): ImageItem[] => {
if (!character?.granblueId) return []
@ -88,8 +91,7 @@
const variants = ['detail', 'grid', 'main', 'square'] as const
const images: ImageItem[] = []
// Determine available poses based on uncap level
// _01 = Base, _02 = MLB (3*), _03 = FLB (5*), _04 = Transcendence
// Only include poses that are available - _01 = Base, _02 = MLB (3*), _03 = FLB (5*), _04 = Transcendence
const poses: { id: string; label: string }[] = [
{ id: '01', label: 'Base' },
{ id: '02', label: 'MLB' }
@ -103,19 +105,51 @@
poses.push({ id: '04', label: 'Transcendence' })
}
for (const variant of variants) {
for (const pose of poses) {
for (const pose of poses) {
for (const variant of variants) {
images.push({
url: getCharacterImage(character.granblueId, variant, pose.id),
label: `${variant} (${pose.label})`,
variant,
pose: pose.id
pose: pose.id,
poseLabel: pose.label
})
}
}
return images
})
// Image download handlers
async function handleDownloadImage(size: string, transformation: string | undefined, force: boolean) {
if (!character?.id) return
await entityAdapter.downloadCharacterImage(character.id, size, transformation, force)
}
async function handleDownloadAllPose(pose: string, force: boolean) {
if (!character?.id) return
// Download all sizes for this pose
for (const size of characterSizes) {
await entityAdapter.downloadCharacterImage(character.id, size, pose, force)
}
}
async function handleDownloadAllImages(force: boolean) {
if (!character?.id) return
await entityAdapter.downloadCharacterImages(character.id, { force })
}
async function handleDownloadSize(size: string) {
if (!character?.id) return
// Download this size for all available poses
const poses = ['01', '02']
if (character.uncap?.flb) poses.push('03')
if (character.uncap?.transcendence) poses.push('04')
for (const pose of poses) {
await entityAdapter.downloadCharacterImage(character.id, size, pose, false)
}
}
</script>
<div class="page">
@ -128,6 +162,9 @@
editUrl={canEdit ? editUrl : undefined}
{currentTab}
onTabChange={handleTabChange}
onDownloadAllImages={canEdit ? handleDownloadAllImages : undefined}
onDownloadSize={canEdit ? handleDownloadSize : undefined}
availableSizes={characterSizes}
>
{#if currentTab === 'info'}
<section class="details">
@ -136,12 +173,18 @@
<CharacterTaxonomySection {character} />
<CharacterStatsSection {character} />
{#if character?.id && character?.granblueId}
<CharacterImagesSection
characterId={character.id}
granblueId={character.granblueId}
{canEdit}
/>
{#if character.releaseDate || character.flbDate || character.ulbDate}
<DetailsContainer title="Dates">
{#if character.releaseDate}
<DetailItem label="Release Date" value={character.releaseDate} />
{/if}
{#if character.flbDate}
<DetailItem label="FLB Date" value={character.flbDate} />
{/if}
{#if character.ulbDate}
<DetailItem label="ULB Date" value={character.ulbDate} />
{/if}
</DetailsContainer>
{/if}
{#if relatedQuery.data?.length}
@ -162,13 +205,26 @@
{/if}
</section>
{:else if currentTab === 'images'}
<EntityImagesTab images={characterImages} />
<EntityImagesTab
images={characterImages}
{canEdit}
onDownloadImage={canEdit ? handleDownloadImage : undefined}
onDownloadAllPose={canEdit ? handleDownloadAllPose : undefined}
/>
{:else if currentTab === 'raw'}
<EntityRawDataTab
wikiRaw={rawDataQuery.data?.wikiRaw}
gameRawEn={rawDataQuery.data?.gameRawEn}
gameRawJp={rawDataQuery.data?.gameRawJp}
isLoading={rawDataQuery.isLoading}
{canEdit}
onFetchWiki={canEdit && character?.id
? async () => {
const result = await entityAdapter.fetchCharacterWiki(character.id)
rawDataQuery.refetch()
return result
}
: undefined}
/>
{/if}
</DetailScaffold>

View file

@ -19,6 +19,8 @@
import SummonStatsSection from '$lib/features/database/summons/sections/SummonStatsSection.svelte'
import EntityImagesTab from '$lib/features/database/detail/tabs/EntityImagesTab.svelte'
import EntityRawDataTab from '$lib/features/database/detail/tabs/EntityRawDataTab.svelte'
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import { getSummonImage } from '$lib/utils/images'
// Types
@ -69,23 +71,85 @@
return getSummonImage(summon?.granblueId, 'grid')
}
// Available image sizes for summons
const summonSizes = ['detail', 'grid', 'main', 'square', 'wide']
// Generate image items for summon (detail, grid, main, square, wide variants)
// Summons have transformations: Base (no suffix), ULB (_02), Transcendence Stage 1 (_03), Transcendence Stage 5 (_04)
const summonImages = $derived.by((): ImageItem[] => {
if (!summon?.granblueId) return []
const variants = ['detail', 'grid', 'main', 'square', 'wide'] as const
const images: ImageItem[] = []
for (const variant of variants) {
images.push({
url: getSummonImage(summon.granblueId, variant),
label: variant,
variant
})
// Only include transformations that are available
const transformations: { id: string; label: string; suffix?: string }[] = [
{ id: '01', label: 'Base', suffix: undefined }
]
if (summon.uncap?.ulb) {
transformations.push({ id: '02', label: 'ULB', suffix: '02' })
}
if (summon.uncap?.transcendence) {
transformations.push(
{ id: '03', label: 'Transcendence (1)', suffix: '03' },
{ id: '04', label: 'Transcendence (5)', suffix: '04' }
)
}
for (const transformation of transformations) {
for (const variant of variants) {
images.push({
url: getSummonImage(summon.granblueId, variant, transformation.suffix),
label: `${variant} (${transformation.label})`,
variant,
pose: transformation.id,
poseLabel: transformation.label
})
}
}
return images
})
// Image download handlers
async function handleDownloadImage(size: string, transformation: string | undefined, force: boolean) {
if (!summon?.id) return
// For summons, '01' means base (no transformation suffix)
const trans = transformation === '01' ? undefined : transformation
await entityAdapter.downloadSummonImage(summon.id, size, trans, force)
}
async function handleDownloadAllPose(pose: string, force: boolean) {
if (!summon?.id) return
const trans = pose === '01' ? undefined : pose
// Download all sizes for this pose
for (const size of summonSizes) {
await entityAdapter.downloadSummonImage(summon.id, size, trans, force)
}
}
async function handleDownloadAllImages(force: boolean) {
if (!summon?.id) return
await entityAdapter.downloadSummonImages(summon.id, { force })
}
async function handleDownloadSize(size: string) {
if (!summon?.id) return
// Download this size for all available transformations
const transformations: (string | undefined)[] = [undefined]
if (summon.uncap?.ulb) {
transformations.push('02')
}
if (summon.uncap?.transcendence) {
transformations.push('03', '04')
}
for (const trans of transformations) {
await entityAdapter.downloadSummonImage(summon.id, size, trans, false)
}
}
</script>
<div class="page">
@ -98,6 +162,9 @@
editUrl={canEdit ? editUrl : undefined}
{currentTab}
onTabChange={handleTabChange}
onDownloadAllImages={canEdit ? handleDownloadAllImages : undefined}
onDownloadSize={canEdit ? handleDownloadSize : undefined}
availableSizes={summonSizes}
>
{#if currentTab === 'info'}
<section class="details">
@ -147,15 +214,45 @@
</div>
{/if}
</div>
{#if summon.releaseDate || summon.flbDate || summon.ulbDate || summon.transcendenceDate}
<DetailsContainer title="Dates">
{#if summon.releaseDate}
<DetailItem label="Release Date" value={summon.releaseDate} />
{/if}
{#if summon.flbDate}
<DetailItem label="FLB Date" value={summon.flbDate} />
{/if}
{#if summon.ulbDate}
<DetailItem label="ULB Date" value={summon.ulbDate} />
{/if}
{#if summon.transcendenceDate}
<DetailItem label="Transcendence Date" value={summon.transcendenceDate} />
{/if}
</DetailsContainer>
{/if}
</section>
{:else if currentTab === 'images'}
<EntityImagesTab images={summonImages} />
<EntityImagesTab
images={summonImages}
{canEdit}
onDownloadImage={canEdit ? handleDownloadImage : undefined}
onDownloadAllPose={canEdit ? handleDownloadAllPose : undefined}
/>
{:else if currentTab === 'raw'}
<EntityRawDataTab
wikiRaw={rawDataQuery.data?.wikiRaw}
gameRawEn={rawDataQuery.data?.gameRawEn}
gameRawJp={rawDataQuery.data?.gameRawJp}
isLoading={rawDataQuery.isLoading}
{canEdit}
onFetchWiki={canEdit && summon?.id
? async () => {
const result = await entityAdapter.fetchSummonWiki(summon.id)
rawDataQuery.refetch()
return result
}
: undefined}
/>
{/if}
</DetailScaffold>

View file

@ -19,6 +19,8 @@
import WeaponStatsSection from '$lib/features/database/weapons/sections/WeaponStatsSection.svelte'
import EntityImagesTab from '$lib/features/database/detail/tabs/EntityImagesTab.svelte'
import EntityRawDataTab from '$lib/features/database/detail/tabs/EntityRawDataTab.svelte'
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import { getWeaponGridImage, getWeaponImage as getWeaponImageUrl } from '$lib/utils/images'
// Types
@ -69,23 +71,78 @@
return getWeaponGridImage(weapon?.granblueId, weapon?.element, weapon?.instanceElement)
}
// Available image sizes for weapons
const weaponSizes = ['base', 'grid', 'main', 'square']
// Generate image items for weapon (base, grid, main, square variants)
// Weapons have transformations: Base (no suffix), Transcendence Stage 1 (_02), Transcendence Stage 5 (_03)
const weaponImages = $derived.by((): ImageItem[] => {
if (!weapon?.granblueId) return []
const variants = ['base', 'grid', 'main', 'square'] as const
const images: ImageItem[] = []
for (const variant of variants) {
images.push({
url: getWeaponImageUrl(weapon.granblueId, variant),
label: variant,
variant
})
// Only include transformations that are available
const transformations: { id: string; label: string; suffix?: string }[] = [
{ id: '01', label: 'Base', suffix: undefined }
]
if (weapon.uncap?.transcendence) {
transformations.push(
{ id: '02', label: 'Transcendence (1)', suffix: '02' },
{ id: '03', label: 'Transcendence (5)', suffix: '03' }
)
}
for (const transformation of transformations) {
for (const variant of variants) {
images.push({
url: getWeaponImageUrl(weapon.granblueId, variant, undefined, transformation.suffix),
label: `${variant} (${transformation.label})`,
variant,
pose: transformation.id,
poseLabel: transformation.label
})
}
}
return images
})
// Image download handlers
async function handleDownloadImage(size: string, transformation: string | undefined, force: boolean) {
if (!weapon?.id) return
// For weapons, '01' means base (no transformation suffix)
const trans = transformation === '01' ? undefined : transformation
await entityAdapter.downloadWeaponImage(weapon.id, size, trans, force)
}
async function handleDownloadAllPose(pose: string, force: boolean) {
if (!weapon?.id) return
const trans = pose === '01' ? undefined : pose
// Download all sizes for this pose
for (const size of weaponSizes) {
await entityAdapter.downloadWeaponImage(weapon.id, size, trans, force)
}
}
async function handleDownloadAllImages(force: boolean) {
if (!weapon?.id) return
await entityAdapter.downloadWeaponImages(weapon.id, { force })
}
async function handleDownloadSize(size: string) {
if (!weapon?.id) return
// Download this size for all available transformations
const transformations: (string | undefined)[] = [undefined]
if (weapon.uncap?.transcendence) {
transformations.push('02', '03')
}
for (const trans of transformations) {
await entityAdapter.downloadWeaponImage(weapon.id, size, trans, false)
}
}
</script>
<div class="page">
@ -98,6 +155,9 @@
editUrl={canEdit ? editUrl : undefined}
{currentTab}
onTabChange={handleTabChange}
onDownloadAllImages={canEdit ? handleDownloadAllImages : undefined}
onDownloadSize={canEdit ? handleDownloadSize : undefined}
availableSizes={weaponSizes}
>
{#if currentTab === 'info'}
<section class="details">
@ -123,15 +183,45 @@
{/if}
</div>
</div>
{#if weapon.releaseDate || weapon.flbDate || weapon.ulbDate || weapon.transcendenceDate}
<DetailsContainer title="Dates">
{#if weapon.releaseDate}
<DetailItem label="Release Date" value={weapon.releaseDate} />
{/if}
{#if weapon.flbDate}
<DetailItem label="FLB Date" value={weapon.flbDate} />
{/if}
{#if weapon.ulbDate}
<DetailItem label="ULB Date" value={weapon.ulbDate} />
{/if}
{#if weapon.transcendenceDate}
<DetailItem label="Transcendence Date" value={weapon.transcendenceDate} />
{/if}
</DetailsContainer>
{/if}
</section>
{:else if currentTab === 'images'}
<EntityImagesTab images={weaponImages} />
<EntityImagesTab
images={weaponImages}
{canEdit}
onDownloadImage={canEdit ? handleDownloadImage : undefined}
onDownloadAllPose={canEdit ? handleDownloadAllPose : undefined}
/>
{:else if currentTab === 'raw'}
<EntityRawDataTab
wikiRaw={rawDataQuery.data?.wikiRaw}
gameRawEn={rawDataQuery.data?.gameRawEn}
gameRawJp={rawDataQuery.data?.gameRawJp}
isLoading={rawDataQuery.isLoading}
{canEdit}
onFetchWiki={canEdit && weapon?.id
? async () => {
const result = await entityAdapter.fetchWeaponWiki(weapon.id)
rawDataQuery.refetch()
return result
}
: undefined}
/>
{/if}
</DetailScaffold>