add CharacterTag component, show tags in db list
This commit is contained in:
parent
d1376396b4
commit
cbef8f5518
3 changed files with 251 additions and 6 deletions
67
src/lib/components/database/cells/CharacterNameCell.svelte
Normal file
67
src/lib/components/database/cells/CharacterNameCell.svelte
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Cell } from 'wx-svelte-grid'
|
||||||
|
import type { Character } from '$lib/types/api/entities'
|
||||||
|
import CharacterTag from '$lib/components/tags/CharacterTag.svelte'
|
||||||
|
|
||||||
|
const { row }: Cell = $props()
|
||||||
|
|
||||||
|
// Cast row to Character type for type safety
|
||||||
|
const character = row as Character
|
||||||
|
|
||||||
|
// Get display name
|
||||||
|
const displayName = $derived.by(() => {
|
||||||
|
const nameObj = character.name
|
||||||
|
if (!nameObj) return '—'
|
||||||
|
if (typeof nameObj === 'string') return nameObj
|
||||||
|
return nameObj.en || nameObj.ja || '—'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if character has season (seasonal variant)
|
||||||
|
const hasSeason = $derived(
|
||||||
|
character.season !== undefined && character.season !== null && character.season > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if character has series (need to check for non-empty array)
|
||||||
|
const hasSeries = $derived.by(() => {
|
||||||
|
if (!character.series || !Array.isArray(character.series)) return false
|
||||||
|
return character.series.length > 0
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="name-cell">
|
||||||
|
<span class="name">{displayName}</span>
|
||||||
|
{#if hasSeason || hasSeries}
|
||||||
|
<div class="tags">
|
||||||
|
{#if hasSeason}
|
||||||
|
<CharacterTag {character} type="season" />
|
||||||
|
{/if}
|
||||||
|
{#if hasSeries}
|
||||||
|
<CharacterTag {character} type="series" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
@use '$src/themes/typography' as *;
|
||||||
|
|
||||||
|
.name-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-half;
|
||||||
|
padding: $unit-half 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: $medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: $unit-half;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
182
src/lib/components/tags/CharacterTag.svelte
Normal file
182
src/lib/components/tags/CharacterTag.svelte
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Character } from '$lib/types/api/entities'
|
||||||
|
import type { CharacterSeriesRef } from '$lib/types/api/characterSeries'
|
||||||
|
import { getElementLabel } from '$lib/utils/element'
|
||||||
|
import { CHARACTER_SEASON_NAMES, CHARACTER_SERIES_NAMES } from '$lib/types/enums'
|
||||||
|
|
||||||
|
type TagType = 'element' | 'season' | 'series'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** The character to display the tag for */
|
||||||
|
character: Character
|
||||||
|
/** Which characteristic to display */
|
||||||
|
type: TagType
|
||||||
|
/** Optional specific series index to display (for multi-series characters) */
|
||||||
|
seriesIndex?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
let { character, type, seriesIndex = 0 }: Props = $props()
|
||||||
|
|
||||||
|
// Get element class name for styling
|
||||||
|
const elementClass = $derived.by(() => {
|
||||||
|
const element = character.element
|
||||||
|
switch (element) {
|
||||||
|
case 1:
|
||||||
|
return 'wind'
|
||||||
|
case 2:
|
||||||
|
return 'fire'
|
||||||
|
case 3:
|
||||||
|
return 'water'
|
||||||
|
case 4:
|
||||||
|
return 'earth'
|
||||||
|
case 5:
|
||||||
|
return 'dark'
|
||||||
|
case 6:
|
||||||
|
return 'light'
|
||||||
|
default:
|
||||||
|
return 'neutral'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get the display text based on tag type
|
||||||
|
const displayText = $derived.by(() => {
|
||||||
|
switch (type) {
|
||||||
|
case 'element':
|
||||||
|
return getElementLabel(character.element)
|
||||||
|
|
||||||
|
case 'season':
|
||||||
|
if (character.season === undefined || character.season === null) return null
|
||||||
|
return CHARACTER_SEASON_NAMES[character.season] ?? null
|
||||||
|
|
||||||
|
case 'series':
|
||||||
|
// Handle array of CharacterSeriesRef objects
|
||||||
|
if (character.series && Array.isArray(character.series)) {
|
||||||
|
const seriesArray = character.series as (number | CharacterSeriesRef)[]
|
||||||
|
const seriesValue = seriesArray[seriesIndex]
|
||||||
|
|
||||||
|
if (seriesValue === undefined) return null
|
||||||
|
|
||||||
|
// Check if it's an object (CharacterSeriesRef) or number
|
||||||
|
if (typeof seriesValue === 'object' && seriesValue !== null && 'name' in seriesValue) {
|
||||||
|
return seriesValue.name.en
|
||||||
|
}
|
||||||
|
// Legacy number format
|
||||||
|
if (typeof seriesValue === 'number') {
|
||||||
|
return CHARACTER_SERIES_NAMES[seriesValue] ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to seriesNames if available
|
||||||
|
if (character.seriesNames && character.seriesNames[seriesIndex]) {
|
||||||
|
return character.seriesNames[seriesIndex]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Only render if we have text to display
|
||||||
|
const shouldRender = $derived(displayText !== null && displayText !== '—')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if shouldRender}
|
||||||
|
<span class="character-tag {elementClass}">
|
||||||
|
{displayText}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/colors' as *;
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
@use '$src/themes/typography' as *;
|
||||||
|
@use '$src/themes/layout' as *;
|
||||||
|
|
||||||
|
.character-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: $unit-fourth $unit;
|
||||||
|
border-radius: $item-corner-small;
|
||||||
|
font-size: $font-tiny;
|
||||||
|
font-weight: $medium;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
// Element-specific styling
|
||||||
|
&.wind {
|
||||||
|
background-color: $wind-bg-20;
|
||||||
|
color: $wind-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fire {
|
||||||
|
background-color: $fire-bg-20;
|
||||||
|
color: $fire-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.water {
|
||||||
|
background-color: $water-bg-20;
|
||||||
|
color: $water-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
background-color: $earth-bg-20;
|
||||||
|
color: $earth-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background-color: $dark-bg-20;
|
||||||
|
color: $dark-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background-color: $light-bg-20;
|
||||||
|
color: $light-text-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.neutral {
|
||||||
|
background-color: $grey-80;
|
||||||
|
color: $grey-40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode adjustments
|
||||||
|
:global(.dark) .character-tag {
|
||||||
|
&.wind {
|
||||||
|
background-color: $wind-text-10;
|
||||||
|
color: $wind-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fire {
|
||||||
|
background-color: $fire-text-10;
|
||||||
|
color: $fire-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.water {
|
||||||
|
background-color: $water-text-10;
|
||||||
|
color: $water-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
background-color: $earth-text-10;
|
||||||
|
color: $earth-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background-color: $dark-text-10;
|
||||||
|
color: $dark-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background-color: $light-text-10;
|
||||||
|
color: $light-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.neutral {
|
||||||
|
background-color: $grey-30;
|
||||||
|
color: $grey-80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
// Svelte components
|
// Svelte components
|
||||||
import CharacterImageCell from '$lib/components/database/cells/CharacterImageCell.svelte'
|
import CharacterImageCell from '$lib/components/database/cells/CharacterImageCell.svelte'
|
||||||
|
import CharacterNameCell from '$lib/components/database/cells/CharacterNameCell.svelte'
|
||||||
import CharacterUncapCell from '$lib/components/database/cells/CharacterUncapCell.svelte'
|
import CharacterUncapCell from '$lib/components/database/cells/CharacterUncapCell.svelte'
|
||||||
import DatabaseGridWithProvider from '$lib/components/database/DatabaseGridWithProvider.svelte'
|
import DatabaseGridWithProvider from '$lib/components/database/DatabaseGridWithProvider.svelte'
|
||||||
import ElementCell from '$lib/components/database/cells/ElementCell.svelte'
|
import ElementCell from '$lib/components/database/cells/ElementCell.svelte'
|
||||||
|
|
@ -28,12 +29,7 @@
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
flexgrow: 1,
|
flexgrow: 1,
|
||||||
sort: true,
|
sort: true,
|
||||||
template: (nameObj: { en: any; ja: any }) => {
|
cell: CharacterNameCell
|
||||||
if (!nameObj) return '—'
|
|
||||||
if (typeof nameObj === 'string') return nameObj
|
|
||||||
// Handle {en: "...", ja: "..."} structure
|
|
||||||
return nameObj.en || nameObj.ja || '—'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'rarity',
|
id: 'rarity',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue