add season, series, gacha_available, recruited_by to character database views
This commit is contained in:
parent
96f040a91b
commit
cf694bb1ce
3 changed files with 140 additions and 8 deletions
|
|
@ -11,6 +11,9 @@ export const CharacterEditSchema = z.object({
|
|||
gender: z.number().int().min(0),
|
||||
proficiency1: z.number().int().min(0),
|
||||
proficiency2: z.number().int().min(0),
|
||||
season: z.number().int().min(1).nullable(),
|
||||
series: z.array(z.number().int().min(1)),
|
||||
gacha_available: z.boolean(),
|
||||
min_hp: z.number().int().min(0),
|
||||
max_hp: z.number().int().min(0),
|
||||
max_hp_flb: z.number().int().min(0),
|
||||
|
|
@ -28,7 +31,7 @@ export type CharacterEdit = z.infer<typeof CharacterEditSchema>
|
|||
export function toEditData(model: any): CharacterEdit {
|
||||
return {
|
||||
name: model?.name ?? '',
|
||||
granblue_id: model?.granblue_id ?? '',
|
||||
granblue_id: model?.granblueId ?? model?.granblue_id ?? '',
|
||||
rarity: model?.rarity ?? 1,
|
||||
element: model?.element ?? 0,
|
||||
race1: model?.race?.[0] ?? null,
|
||||
|
|
@ -36,12 +39,16 @@ export function toEditData(model: any): CharacterEdit {
|
|||
gender: model?.gender ?? 0,
|
||||
proficiency1: model?.proficiency?.[0] ?? 0,
|
||||
proficiency2: model?.proficiency?.[1] ?? 0,
|
||||
min_hp: model?.hp?.min_hp ?? 0,
|
||||
max_hp: model?.hp?.max_hp ?? 0,
|
||||
max_hp_flb: model?.hp?.max_hp_flb ?? 0,
|
||||
min_atk: model?.atk?.min_atk ?? 0,
|
||||
max_atk: model?.atk?.max_atk ?? 0,
|
||||
max_atk_flb: model?.atk?.max_atk_flb ?? 0,
|
||||
season: model?.season ?? null,
|
||||
series: model?.series ?? [],
|
||||
// API returns camelCase (gachaAvailable) after transformation
|
||||
gacha_available: model?.gachaAvailable ?? model?.gacha_available ?? true,
|
||||
min_hp: model?.hp?.minHp ?? model?.hp?.min_hp ?? 0,
|
||||
max_hp: model?.hp?.maxHp ?? model?.hp?.max_hp ?? 0,
|
||||
max_hp_flb: model?.hp?.maxHpFlb ?? model?.hp?.max_hp_flb ?? 0,
|
||||
min_atk: model?.atk?.minAtk ?? model?.atk?.min_atk ?? 0,
|
||||
max_atk: model?.atk?.maxAtk ?? model?.atk?.max_atk ?? 0,
|
||||
max_atk_flb: model?.atk?.maxAtkFlb ?? model?.atk?.max_atk_flb ?? 0,
|
||||
flb: model?.uncap?.flb ?? false,
|
||||
ulb: model?.uncap?.ulb ?? false,
|
||||
transcendence: model?.uncap?.transcendence ?? false,
|
||||
|
|
@ -59,6 +66,9 @@ export function toPayload(edit: CharacterEdit) {
|
|||
race: [edit.race1, edit.race2].filter((r) => r !== null && r !== undefined),
|
||||
gender: edit.gender,
|
||||
proficiency: [edit.proficiency1, edit.proficiency2],
|
||||
season: edit.season,
|
||||
series: edit.series,
|
||||
gacha_available: edit.gacha_available,
|
||||
hp: {
|
||||
min_hp: edit.min_hp,
|
||||
max_hp: edit.max_hp,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import SuggestionDetailItem from '$lib/components/ui/SuggestionDetailItem.svelte'
|
||||
import CopyableText from '$lib/components/ui/CopyableText.svelte'
|
||||
import { getRarityLabel, getRarityOptions } from '$lib/utils/rarity'
|
||||
import { getWeaponImage } from '$lib/utils/images'
|
||||
|
||||
interface Props {
|
||||
character: any
|
||||
|
|
@ -30,6 +31,11 @@
|
|||
}: Props = $props()
|
||||
|
||||
const rarityOptions = getRarityOptions()
|
||||
|
||||
function formatPromotions(promotionNames: string[] | undefined): string {
|
||||
if (!promotionNames || promotionNames.length === 0) return '—'
|
||||
return promotionNames.join(', ')
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailsContainer title="Metadata">
|
||||
|
|
@ -64,6 +70,58 @@
|
|||
—
|
||||
{/if}
|
||||
</DetailItem>
|
||||
{#if character.recruitedBy}
|
||||
<DetailItem label="Recruited By">
|
||||
<a href="/database/weapons/{character.recruitedBy.id}" class="recruited-by-link">
|
||||
<img
|
||||
src={getWeaponImage(character.recruitedBy.granblueId, 'square')}
|
||||
alt={character.recruitedBy.name.en || 'Recruiting weapon'}
|
||||
class="recruited-by-image"
|
||||
/>
|
||||
<span class="recruited-by-name">{character.recruitedBy.name.en}</span>
|
||||
</a>
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label="Promotions"
|
||||
sublabel="Gacha pools from recruiting weapon"
|
||||
value={formatPromotions(character.recruitedBy.promotionNames)}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/colors' as colors;
|
||||
@use '$src/themes/spacing' as spacing;
|
||||
@use '$src/themes/typography' as typography;
|
||||
@use '$src/themes/layout' as layout;
|
||||
|
||||
.recruited-by-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: spacing.$unit;
|
||||
text-decoration: none;
|
||||
color: colors.$grey-30;
|
||||
|
||||
&:hover .recruited-by-image {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover .recruited-by-name {
|
||||
color: colors.$blue;
|
||||
}
|
||||
}
|
||||
|
||||
.recruited-by-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: layout.$item-corner-small;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.recruited-by-name {
|
||||
font-size: typography.$font-regular;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,20 @@
|
|||
import SuggestionDetailItem from '$lib/components/ui/SuggestionDetailItem.svelte'
|
||||
import ElementLabel from '$lib/components/labels/ElementLabel.svelte'
|
||||
import ProficiencyLabel from '$lib/components/labels/ProficiencyLabel.svelte'
|
||||
import { getElementOptions } from '$lib/utils/element'
|
||||
import { getElementLabel, getElementOptions } from '$lib/utils/element'
|
||||
import { getRaceLabel, getRaceOptions } from '$lib/utils/race'
|
||||
import { getGenderLabel, getGenderOptions } from '$lib/utils/gender'
|
||||
import { getProficiencyOptions } from '$lib/utils/proficiency'
|
||||
import {
|
||||
CharacterSeason,
|
||||
CharacterSeries,
|
||||
CHARACTER_SEASON_NAMES,
|
||||
CHARACTER_SERIES_NAMES,
|
||||
getSeasonName,
|
||||
getSeriesNames
|
||||
} from '$lib/types/enums'
|
||||
|
||||
type ElementName = 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light'
|
||||
|
||||
interface Props {
|
||||
character: any
|
||||
|
|
@ -37,6 +47,34 @@
|
|||
const raceOptions = getRaceOptions()
|
||||
const genderOptions = getGenderOptions()
|
||||
const proficiencyOptions = getProficiencyOptions()
|
||||
|
||||
// Season options (nullable, so include a "None" option)
|
||||
const seasonOptions = [
|
||||
{ value: 0, label: 'None' },
|
||||
...Object.entries(CHARACTER_SEASON_NAMES).map(([value, label]) => ({
|
||||
value: Number(value),
|
||||
label
|
||||
}))
|
||||
]
|
||||
|
||||
// Series options for multiselect
|
||||
const seriesOptions = Object.entries(CHARACTER_SERIES_NAMES).map(([value, label]) => ({
|
||||
value: Number(value),
|
||||
label
|
||||
}))
|
||||
|
||||
// Get element name for checkbox theming
|
||||
const elementName = $derived.by((): ElementName | undefined => {
|
||||
const el = editMode ? editData?.element : character?.element
|
||||
const label = getElementLabel(el)
|
||||
return label !== '—' && label !== 'Null' ? (label.toLowerCase() as ElementName) : undefined
|
||||
})
|
||||
|
||||
// Format series for display
|
||||
function formatSeriesDisplay(series: number[]): string {
|
||||
if (!series || series.length === 0) return '—'
|
||||
return getSeriesNames(series).join(', ')
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailsContainer title="Details">
|
||||
|
|
@ -107,6 +145,29 @@
|
|||
onAcceptSuggestion={() => onAcceptSuggestion?.('proficiency2', suggestions?.proficiency2)}
|
||||
onDismissSuggestion={() => onDismissSuggestion?.('proficiency2')}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Season"
|
||||
bind:value={editData.season}
|
||||
editable={true}
|
||||
type="select"
|
||||
options={seasonOptions}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Series"
|
||||
bind:value={editData.series}
|
||||
editable={true}
|
||||
type="multiselect"
|
||||
options={seriesOptions}
|
||||
element={elementName}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Gacha Available"
|
||||
sublabel="Can be pulled from gacha"
|
||||
bind:value={editData.gacha_available}
|
||||
editable={true}
|
||||
type="checkbox"
|
||||
element={elementName}
|
||||
/>
|
||||
{:else}
|
||||
<DetailItem label="Element">
|
||||
<ElementLabel element={character.element} size="medium" />
|
||||
|
|
@ -120,6 +181,9 @@
|
|||
<DetailItem label="Proficiency 2">
|
||||
<ProficiencyLabel proficiency={character.proficiency?.[1] ?? 0} size="medium" />
|
||||
</DetailItem>
|
||||
<DetailItem label="Season" value={getSeasonName(character.season) || '—'} />
|
||||
<DetailItem label="Series" value={formatSeriesDisplay(character.series)} />
|
||||
<DetailItem label="Gacha Available" value={character.gachaAvailable ? 'Yes' : 'No'} />
|
||||
{/if}
|
||||
</DetailsContainer>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue