complete handleSave for weapon modifiers, update key select to use slug

This commit is contained in:
Justin Edmund 2025-12-03 11:59:16 -08:00
parent c5f6963ca7
commit a251240331
3 changed files with 137 additions and 45 deletions

View file

@ -10,6 +10,7 @@
import AxSkillSelect from './edit/AxSkillSelect.svelte' import AxSkillSelect from './edit/AxSkillSelect.svelte'
import Button from '$lib/components/ui/Button.svelte' import Button from '$lib/components/ui/Button.svelte'
import { getElementIcon } from '$lib/utils/images' import { getElementIcon } from '$lib/utils/images'
import { seriesHasWeaponKeys, getSeriesSlug } from '$lib/utils/weaponSeries'
interface Props { interface Props {
weapon: GridWeapon weapon: GridWeapon
@ -42,23 +43,24 @@
// Weapon data shortcuts // Weapon data shortcuts
const weaponData = $derived(weapon.weapon) const weaponData = $derived(weapon.weapon)
const canChangeElement = $derived(weaponData?.element === 0) const canChangeElement = $derived(weaponData?.element === 0)
const series = $derived(weaponData?.series ?? 0) const series = $derived(weaponData?.series)
const seriesSlug = $derived(getSeriesSlug(series))
const transcendenceStep = $derived(weapon.transcendenceStep ?? 0) const transcendenceStep = $derived(weapon.transcendenceStep ?? 0)
// Weapon key config keyed by WEAPON series // Weapon key slot configuration by series slug
// Maps weapon.series → { slots, keySeries } where keySeries is what weapon_keys API expects // Maps series slug → number of weapon key slots
const WEAPON_KEY_SERIES: Record<number, { name: string; slots: number; keySeries: number }> = { const WEAPON_KEY_SLOTS: Record<string, number> = {
2: { name: 'Dark Opus', slots: 2, keySeries: 3 }, // Pendulum (slot 0) + Chain/Pendulum (slot 1) 'dark-opus': 2, // Pendulum (slot 0) + Chain/Pendulum (slot 1)
3: { name: 'Ultima', slots: 3, keySeries: 13 }, // Gauph Key (slot 0) + Ultima Key (slot 1) + Gate (slot 2) 'ultima': 3, // Gauph Key (slot 0) + Ultima Key (slot 1) + Gate (slot 2)
17: { name: 'Draconic', slots: 2, keySeries: 27 }, // Teluma (slot 0) + Teluma (slot 1) 'draconic': 2, // Teluma (slot 0) + Teluma (slot 1)
22: { name: 'Astral', slots: 1, keySeries: 19 }, // Emblem (slot 0) 'draconic-providence': 2, // Same as Draconic
34: { name: 'Superlative', slots: 2, keySeries: 40 } // Teluma (slot 0) + Teluma (slot 1) 'superlative': 2, // Teluma (slot 0) + Teluma (slot 1)
// Add more as needed
} }
const weaponKeyConfig = $derived(WEAPON_KEY_SERIES[series]) // Check if series has weapon keys using the utility (handles both formats)
const hasWeaponKeys = $derived(!!weaponKeyConfig) const hasWeaponKeys = $derived(seriesHasWeaponKeys(series))
const keySlotCount = $derived(weaponKeyConfig?.slots ?? 0) const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0)
const keySeries = $derived(weaponKeyConfig?.keySeries ?? 0)
const hasAxSkills = $derived(weaponData?.ax === true) const hasAxSkills = $derived(weaponData?.ax === true)
const axType = $derived(weaponData?.axType ?? 1) const axType = $derived(weaponData?.axType ?? 1)
@ -95,16 +97,92 @@
return '—' return '—'
} }
function handleSave() { // Build the update payload for the API
const updates: Partial<GridWeapon> = {} // Uses flat key IDs rather than nested arrays, as expected by the API
interface WeaponUpdatePayload {
element?: number
weaponKey1Id?: string | null
weaponKey2Id?: string | null
weaponKey3Id?: string | null
awakeningId?: string | null
awakeningLevel?: number
axModifier1?: number | null
axStrength1?: number | null
axModifier2?: number | null
axStrength2?: number | null
}
function handleSave() {
const updates: WeaponUpdatePayload = {}
// Element change (only for element-changeable weapons)
if (canChangeElement && element !== weapon.element) { if (canChangeElement && element !== weapon.element) {
updates.element = element updates.element = element
} }
// TODO: Add weapon keys, AX skills, awakening updates // Weapon keys - send individual key IDs
if (hasWeaponKeys) {
const originalKey1 = weapon.weaponKeys?.[0]?.id
const originalKey2 = weapon.weaponKeys?.[1]?.id
const originalKey3 = weapon.weaponKeys?.[2]?.id
onSave?.(updates) if (weaponKey1 !== originalKey1) {
updates.weaponKey1Id = weaponKey1 ?? null
}
if (weaponKey2 !== originalKey2) {
updates.weaponKey2Id = weaponKey2 ?? null
}
if (weaponKey3 !== originalKey3) {
updates.weaponKey3Id = weaponKey3 ?? null
}
}
// Awakening - send awakening ID and level
if (hasAwakening) {
const originalAwakeningId = weapon.awakening?.type?.id
const originalLevel = weapon.awakening?.level ?? 1
if (selectedAwakening?.id !== originalAwakeningId) {
updates.awakeningId = selectedAwakening?.id ?? null
}
if (awakeningLevel !== originalLevel) {
updates.awakeningLevel = awakeningLevel
}
}
// AX skills - send modifier/strength pairs
if (hasAxSkills) {
const originalAx = weapon.ax ?? [
{ modifier: -1, strength: 0 },
{ modifier: -1, strength: 0 }
]
const ax1 = axSkills[0]
const ax2 = axSkills[1]
const origAx1 = originalAx[0]
const origAx2 = originalAx[1]
if (ax1?.modifier !== origAx1?.modifier) {
updates.axModifier1 = ax1?.modifier ?? null
}
if (ax1?.strength !== origAx1?.strength) {
updates.axStrength1 = ax1?.strength ?? null
}
if (ax2?.modifier !== origAx2?.modifier) {
updates.axModifier2 = ax2?.modifier ?? null
}
if (ax2?.strength !== origAx2?.strength) {
updates.axStrength2 = ax2?.strength ?? null
}
}
// Only call onSave if there are actual updates
if (Object.keys(updates).length > 0) {
onSave?.(updates as Partial<GridWeapon>)
} else {
// No changes, just close the panel
onCancel?.()
}
} }
function handleCancel() { function handleCancel() {
@ -148,12 +226,12 @@
</DetailsSection> </DetailsSection>
{/if} {/if}
{#if hasWeaponKeys} {#if hasWeaponKeys && seriesSlug}
<DetailsSection title="Weapon Keys"> <DetailsSection title="Weapon Keys">
<div class="key-selects"> <div class="key-selects">
{#if keySlotCount >= 1} {#if keySlotCount >= 1}
<WeaponKeySelect <WeaponKeySelect
series={keySeries} {seriesSlug}
slot={0} slot={0}
bind:value={weaponKey1} bind:value={weaponKey1}
{transcendenceStep} {transcendenceStep}
@ -161,7 +239,7 @@
{/if} {/if}
{#if keySlotCount >= 2} {#if keySlotCount >= 2}
<WeaponKeySelect <WeaponKeySelect
series={keySeries} {seriesSlug}
slot={1} slot={1}
bind:value={weaponKey2} bind:value={weaponKey2}
{transcendenceStep} {transcendenceStep}
@ -169,7 +247,7 @@
{/if} {/if}
{#if keySlotCount >= 3} {#if keySlotCount >= 3}
<WeaponKeySelect <WeaponKeySelect
series={keySeries} {seriesSlug}
slot={2} slot={2}
bind:value={weaponKey3} bind:value={weaponKey3}
{transcendenceStep} {transcendenceStep}

View file

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { createQuery } from '@tanstack/svelte-query' import { createQuery } from '@tanstack/svelte-query'
import { entityQueries } from '$lib/api/queries/entity.queries' import { entityAdapter } from '$lib/api/adapters/entity.adapter'
import type { WeaponKey } from '$lib/api/adapters/entity.adapter' import type { WeaponKey } from '$lib/api/adapters/entity.adapter'
import Select from '$lib/components/ui/Select.svelte' import Select from '$lib/components/ui/Select.svelte'
import { queryOptions } from '@tanstack/svelte-query'
interface Props { interface Props {
/** The weapon series (determines which keys are available) */ /** The weapon series slug (determines which keys are available) */
series: number seriesSlug?: string
/** The slot number (1, 2, or 3) */ /** The slot number (0, 1, or 2) */
slot: number slot: number
/** Currently selected weapon key ID */ /** Currently selected weapon key ID */
value?: string value?: string
@ -17,42 +18,54 @@
transcendenceStep?: number transcendenceStep?: number
} }
let { series, slot, value = $bindable(), onchange, transcendenceStep = 0 }: Props = $props() let { seriesSlug, slot, value = $bindable(), onchange, transcendenceStep = 0 }: Props = $props()
// Key type names based on series and slot (0-based indexing) // Key type names based on series slug and slot (0-based indexing)
const KEY_TYPE_NAMES: Record<number, Record<number, { en: string; ja: string }>> = { const KEY_TYPE_NAMES: Record<string, Record<number, { en: string; ja: string }>> = {
// Dark Opus (series 3) // Dark Opus
3: { 'dark-opus': {
0: { en: 'Pendulum', ja: 'ペンデュラム' }, 0: { en: 'Pendulum', ja: 'ペンデュラム' },
1: { en: 'Pendulum/Chain', ja: 'ペンデュラム/チェイン' } 1: { en: 'Pendulum/Chain', ja: 'ペンデュラム/チェイン' }
}, },
// Draconic (series 27) // Draconic
27: { 'draconic': {
0: { en: 'Teluma', ja: 'テルマ' }, 0: { en: 'Teluma', ja: 'テルマ' },
1: { en: 'Teluma', ja: 'テルマ' } 1: { en: 'Teluma', ja: 'テルマ' }
}, },
// Ultima (series 13) // Draconic Providence
13: { 'draconic-providence': {
0: { en: 'Teluma', ja: 'テルマ' },
1: { en: 'Teluma', ja: 'テルマ' }
},
// Ultima
'ultima': {
0: { en: 'Gauph Key', ja: 'ガフスキー' }, 0: { en: 'Gauph Key', ja: 'ガフスキー' },
1: { en: 'Ultima Key', ja: 'ガフスキーΩ' }, 1: { en: 'Ultima Key', ja: 'ガフスキーΩ' },
2: { en: 'Gate of Omnipotence', ja: 'ガフスキー' } 2: { en: 'Gate of Omnipotence', ja: 'ガフスキー' }
}, },
// Astral (series 19) // Superlative
19: { 'superlative': {
0: { en: 'Emblem', ja: 'エンブレム' }
},
// Superlative (series 40)
40: {
0: { en: 'Teluma', ja: 'テルマ' }, 0: { en: 'Teluma', ja: 'テルマ' },
1: { en: 'Teluma', ja: 'テルマ' } 1: { en: 'Teluma', ja: 'テルマ' }
} }
} }
// Fetch weapon keys for this series and slot // Fetch weapon keys for this series slug and slot
const weaponKeysQuery = createQuery(() => entityQueries.weaponKeys({ series, slot })) const weaponKeysQuery = createQuery(() =>
queryOptions({
queryKey: ['weaponKeys', 'slug', seriesSlug, slot] as const,
queryFn: async () => {
if (!seriesSlug) return []
return entityAdapter.getWeaponKeys({ seriesSlug, slot })
},
enabled: !!seriesSlug,
staleTime: 1000 * 60 * 60,
gcTime: 1000 * 60 * 60 * 24
})
)
// Get the key type name for this series/slot // Get the key type name for this series/slot
const keyTypeName = $derived(KEY_TYPE_NAMES[series]?.[slot]?.en ?? 'Key') const keyTypeName = $derived(seriesSlug ? (KEY_TYPE_NAMES[seriesSlug]?.[slot]?.en ?? 'Key') : 'Key')
// Group and sort weapon keys // Group and sort weapon keys
const groupedOptions = $derived.by(() => { const groupedOptions = $derived.by(() => {

View file

@ -2,13 +2,14 @@
import { getWeaponKeyImages } from '$lib/utils/modifiers' import { getWeaponKeyImages } from '$lib/utils/modifiers'
import type { WeaponKey } from '$lib/types/api/entities' import type { WeaponKey } from '$lib/types/api/entities'
import type { LocalizedName } from '$lib/types/api/entities' import type { LocalizedName } from '$lib/types/api/entities'
import type { WeaponSeriesRef } from '$lib/types/api/weaponSeries'
interface Props { interface Props {
weaponKeys?: WeaponKey[] weaponKeys?: WeaponKey[]
weaponData?: { weaponData?: {
element?: number element?: number
proficiency?: number | number[] proficiency?: number | number[]
series?: number series?: WeaponSeriesRef | null
name?: LocalizedName name?: LocalizedName
} }
layout?: 'list' | 'grid' layout?: 'list' | 'grid'
@ -32,7 +33,7 @@
return key.slug || 'Weapon Key' return key.slug || 'Weapon Key'
} }
function getSlotLabel(slot: number, series?: number): string { function getSlotLabel(slot: number, series?: WeaponSeriesRef | null): string {
return `Skill ${slot + 1}` return `Skill ${slot + 1}`
} }
</script> </script>