179 lines
4.3 KiB
Svelte
179 lines
4.3 KiB
Svelte
<svelte:options runes={true} />
|
|
|
|
<script lang="ts">
|
|
import type { Awakening } from '$lib/types/api/entities'
|
|
import { NO_AWAKENING } from '$lib/types/api/entities'
|
|
import Select from '$lib/components/ui/Select.svelte'
|
|
import Input from '$lib/components/ui/Input.svelte'
|
|
import DetailRow from '$lib/components/sidebar/details/DetailRow.svelte'
|
|
import { getAwakeningImage } from '$lib/utils/modifiers'
|
|
|
|
interface Props {
|
|
/** Available awakenings for the weapon */
|
|
awakenings: Awakening[]
|
|
/** Currently selected awakening */
|
|
value?: Awakening
|
|
/** Current awakening level */
|
|
level?: number
|
|
/** Maximum awakening level for the weapon */
|
|
maxLevel: number
|
|
/** Called when awakening type changes */
|
|
onAwakeningChange?: (awakening: Awakening | undefined) => void
|
|
/** Called when awakening level changes */
|
|
onLevelChange?: (level: number) => void
|
|
}
|
|
|
|
let {
|
|
awakenings,
|
|
value = undefined,
|
|
level = 1,
|
|
maxLevel,
|
|
onAwakeningChange,
|
|
onLevelChange
|
|
}: Props = $props()
|
|
|
|
// Local state for the selected awakening ID (use id or slug as key)
|
|
let selectedId = $state(value ? (value.id || value.slug || NO_AWAKENING.id) : NO_AWAKENING.id)
|
|
let localLevel = $state(level)
|
|
|
|
// Error state for level input
|
|
let levelError = $state('')
|
|
|
|
// Helper to get a unique identifier for an awakening (use id if available, fallback to slug)
|
|
function getAwakeningKey(awk: Awakening): string {
|
|
return awk.id || awk.slug || 'unknown'
|
|
}
|
|
|
|
// Build options list with NO_AWAKENING first
|
|
const options = $derived.by(() => {
|
|
const sorted = [...awakenings].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
const items: Array<{ value: string; label: string; image?: string }> = sorted.map((awk) => {
|
|
const img = getAwakeningImage({ type: awk, level: 1 })
|
|
return {
|
|
value: getAwakeningKey(awk),
|
|
label: awk.name?.en || awk.name?.ja || 'Unknown',
|
|
image: img ?? undefined
|
|
}
|
|
})
|
|
|
|
// Add NO_AWAKENING at the beginning if not already present
|
|
if (!awakenings.find((a) => getAwakeningKey(a) === NO_AWAKENING.id)) {
|
|
items.unshift({
|
|
value: NO_AWAKENING.id,
|
|
label: NO_AWAKENING.name.en
|
|
})
|
|
}
|
|
|
|
return items
|
|
})
|
|
|
|
// Is the current selection the "No awakening" option?
|
|
const isNoAwakening = $derived(selectedId === NO_AWAKENING.id)
|
|
|
|
// Handle awakening type change
|
|
function handleAwakeningChange(newId: string | undefined) {
|
|
if (!newId) {
|
|
selectedId = NO_AWAKENING.id
|
|
onAwakeningChange?.(undefined)
|
|
return
|
|
}
|
|
|
|
selectedId = newId
|
|
|
|
if (selectedId === NO_AWAKENING.id) {
|
|
onAwakeningChange?.(undefined)
|
|
} else {
|
|
// Find by id first, then by slug (for awakenings with null id)
|
|
const selected = awakenings.find((a) => getAwakeningKey(a) === selectedId)
|
|
onAwakeningChange?.(selected)
|
|
}
|
|
}
|
|
|
|
// Handle level change with validation
|
|
function handleLevelChange(event: Event) {
|
|
const input = event.target as HTMLInputElement
|
|
const newLevel = parseInt(input.value, 10)
|
|
|
|
// Validate the level
|
|
if (isNaN(newLevel)) {
|
|
levelError = 'Please enter a valid number'
|
|
return
|
|
}
|
|
|
|
if (newLevel < 1) {
|
|
levelError = 'Level must be at least 1'
|
|
return
|
|
}
|
|
|
|
if (newLevel > maxLevel) {
|
|
levelError = `Level cannot exceed ${maxLevel}`
|
|
return
|
|
}
|
|
|
|
if (!Number.isInteger(newLevel)) {
|
|
levelError = 'Level must be a whole number'
|
|
return
|
|
}
|
|
|
|
levelError = ''
|
|
localLevel = newLevel
|
|
onLevelChange?.(newLevel)
|
|
}
|
|
</script>
|
|
|
|
<div class="awakening-select">
|
|
<div class="awakening-type">
|
|
<Select
|
|
options={options}
|
|
value={selectedId}
|
|
onValueChange={handleAwakeningChange}
|
|
placeholder="Select awakening"
|
|
size="medium"
|
|
fullWidth
|
|
contained
|
|
/>
|
|
</div>
|
|
|
|
{#if !isNoAwakening}
|
|
<DetailRow label="Level" noHover noPadding>
|
|
<Input
|
|
type="number"
|
|
min={1}
|
|
max={maxLevel}
|
|
step={1}
|
|
value={localLevel}
|
|
oninput={handleLevelChange}
|
|
error={levelError || undefined}
|
|
contained
|
|
variant="number"
|
|
placeholder="1~{maxLevel}"
|
|
/>
|
|
</DetailRow>
|
|
{/if}
|
|
|
|
{#if levelError}
|
|
<p class="level-error">{levelError}</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
@use '$src/themes/colors' as colors;
|
|
@use '$src/themes/spacing' as spacing;
|
|
@use '$src/themes/typography' as typography;
|
|
|
|
.awakening-select {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: spacing.$unit-2x;
|
|
}
|
|
|
|
.awakening-type {
|
|
flex: 1;
|
|
}
|
|
|
|
.level-error {
|
|
margin: 0;
|
|
font-size: typography.$font-small;
|
|
color: colors.$error;
|
|
}
|
|
</style>
|