refactor EditCharacterSidebar to use shared CharacterEditPane
Reduces ~240 lines to ~85 lines by using the shared CharacterEditPane component for edit controls. Both party grid and collection character editing now use the same underlying UI component.
This commit is contained in:
parent
dea784780a
commit
2aa961b5e6
1 changed files with 37 additions and 191 deletions
|
|
@ -1,18 +1,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* EditCharacterSidebar - Edit sidebar for party grid characters
|
||||||
|
*
|
||||||
|
* Uses the shared CharacterEditPane for edit controls.
|
||||||
|
* Handles GridCharacter-specific data transformation and mutations.
|
||||||
|
*/
|
||||||
import type { GridCharacter } from '$lib/types/api/party'
|
import type { GridCharacter } from '$lib/types/api/party'
|
||||||
import type { Awakening } from '$lib/types/api/entities'
|
|
||||||
import DetailsSection from './details/DetailsSection.svelte'
|
|
||||||
import ItemHeader from './details/ItemHeader.svelte'
|
import ItemHeader from './details/ItemHeader.svelte'
|
||||||
import AwakeningSelect from './edit/AwakeningSelect.svelte'
|
import CharacterEditPane, {
|
||||||
import RingsSelect from './edit/RingsSelect.svelte'
|
type CharacterEditValues,
|
||||||
import EarringSelect from './edit/EarringSelect.svelte'
|
type CharacterEditUpdates
|
||||||
import PerpetuityToggle from './edit/PerpetuityToggle.svelte'
|
} from './CharacterEditPane.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
|
||||||
|
|
||||||
interface ExtendedMastery {
|
|
||||||
modifier: number
|
|
||||||
strength: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
character: GridCharacter
|
character: GridCharacter
|
||||||
|
|
@ -24,106 +22,35 @@
|
||||||
|
|
||||||
// Character data shortcut
|
// Character data shortcut
|
||||||
const characterData = $derived(character.character)
|
const characterData = $derived(character.character)
|
||||||
const characterElement = $derived(characterData?.element)
|
|
||||||
|
|
||||||
// Awakening state - initialize from existing awakening
|
|
||||||
let selectedAwakening = $state<Awakening | undefined>(character.awakening?.type)
|
|
||||||
let awakeningLevel = $state(character.awakening?.level ?? 1)
|
|
||||||
|
|
||||||
// Rings state - initialize from existing overMastery
|
|
||||||
let rings = $state<ExtendedMastery[]>(
|
|
||||||
character.overMastery ?? [
|
|
||||||
{ modifier: 1, strength: 0 },
|
|
||||||
{ modifier: 2, strength: 0 },
|
|
||||||
{ modifier: 0, strength: 0 },
|
|
||||||
{ modifier: 0, strength: 0 }
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Earring state - initialize from existing aetherialMastery
|
|
||||||
let earring = $state<ExtendedMastery | undefined>(character.aetherialMastery)
|
|
||||||
|
|
||||||
// Perpetuity state - initialize from existing value
|
|
||||||
let perpetuity = $state(character.perpetuity ?? false)
|
|
||||||
|
|
||||||
// Derived conditions for what can be edited
|
|
||||||
// Characters always have maxAwakeningLevel of 10 (not returned by API, but hardcoded)
|
|
||||||
const maxAwakeningLevel = 10
|
|
||||||
const hasAwakening = $derived((characterData?.awakenings?.length ?? 0) > 0)
|
|
||||||
const availableAwakenings = $derived(characterData?.awakenings ?? [])
|
|
||||||
|
|
||||||
// Perpetuity is only available for non-MC characters (position > 0)
|
// Perpetuity is only available for non-MC characters (position > 0)
|
||||||
const canHavePerpetuity = $derived(character.position > 0)
|
const canHavePerpetuity = $derived(character.position > 0)
|
||||||
|
|
||||||
// Awakening slug to UUID map (awakenings come from API with id: null, only slugs)
|
// Convert GridCharacter data to CharacterEditPane format
|
||||||
const AWAKENING_MAP: Record<string, string> = {
|
const currentValues = $derived<CharacterEditValues>({
|
||||||
'character-balanced': 'b1847c82-ece0-4d7a-8af1-c7868d90f34a',
|
awakening: character.awakening
|
||||||
'character-atk': '6e233877-8cda-4c8f-a091-3db6f68749e2',
|
? {
|
||||||
'character-def': 'c95441de-f949-4a62-b02b-101aa2e0a638',
|
type: character.awakening.type,
|
||||||
'character-multi': 'e36b0573-79c3-4dd2-9524-c95def4bbb1a'
|
level: character.awakening.level ?? 1
|
||||||
}
|
|
||||||
|
|
||||||
// Element name for the sidebar theming
|
|
||||||
const ELEMENT_MAP: Record<number, 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light'> = {
|
|
||||||
1: 'wind',
|
|
||||||
2: 'fire',
|
|
||||||
3: 'water',
|
|
||||||
4: 'earth',
|
|
||||||
5: 'dark',
|
|
||||||
6: 'light'
|
|
||||||
}
|
|
||||||
const elementName = $derived(characterElement ? ELEMENT_MAP[characterElement] : undefined)
|
|
||||||
|
|
||||||
function handleSave() {
|
|
||||||
// Build API-formatted updates (field names match Rails API expectations)
|
|
||||||
const updates: Record<string, unknown> = {}
|
|
||||||
|
|
||||||
// Collect awakening updates - convert slug to UUID using AWAKENING_MAP
|
|
||||||
// The awakenings list from API has id: null, only slugs are populated
|
|
||||||
if (hasAwakening) {
|
|
||||||
if (selectedAwakening && selectedAwakening.slug) {
|
|
||||||
const awakeningId = AWAKENING_MAP[selectedAwakening.slug]
|
|
||||||
if (awakeningId) {
|
|
||||||
updates.awakening = {
|
|
||||||
id: awakeningId,
|
|
||||||
level: awakeningLevel
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(`Unknown awakening slug: ${selectedAwakening.slug}`)
|
|
||||||
}
|
}
|
||||||
} else {
|
: null,
|
||||||
updates.awakening = null
|
rings: character.overMastery ?? [
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect rings updates - API expects 'rings' not 'overMastery'
|
|
||||||
updates.rings = rings
|
|
||||||
|
|
||||||
// Collect earring updates - API expects 'earring' not 'aetherialMastery'
|
|
||||||
if (earring) {
|
|
||||||
updates.earring = earring
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect perpetuity updates
|
|
||||||
if (canHavePerpetuity) {
|
|
||||||
updates.perpetuity = perpetuity
|
|
||||||
}
|
|
||||||
|
|
||||||
onSave?.(updates as Partial<GridCharacter>)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel() {
|
|
||||||
// Reset to original values
|
|
||||||
selectedAwakening = character.awakening?.type
|
|
||||||
awakeningLevel = character.awakening?.level ?? 1
|
|
||||||
rings = character.overMastery ?? [
|
|
||||||
{ modifier: 1, strength: 0 },
|
{ modifier: 1, strength: 0 },
|
||||||
{ modifier: 2, strength: 0 },
|
{ modifier: 2, strength: 0 },
|
||||||
{ modifier: 0, strength: 0 },
|
{ modifier: 0, strength: 0 },
|
||||||
{ modifier: 0, strength: 0 }
|
{ modifier: 0, strength: 0 }
|
||||||
]
|
],
|
||||||
earring = character.aetherialMastery
|
earring: character.aetherialMastery ?? undefined,
|
||||||
perpetuity = character.perpetuity ?? false
|
perpetuity: character.perpetuity ?? false
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleSave(updates: CharacterEditUpdates) {
|
||||||
|
// Transform CharacterEditUpdates to GridCharacter API format
|
||||||
|
// The CharacterEditPane already formats awakening with id/level
|
||||||
|
onSave?.(updates as Partial<GridCharacter>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
onCancel?.()
|
onCancel?.()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -137,70 +64,13 @@
|
||||||
gridTranscendence={character.transcendenceStep}
|
gridTranscendence={character.transcendenceStep}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="edit-sections">
|
<CharacterEditPane
|
||||||
{#if hasAwakening && availableAwakenings.length > 0}
|
{characterData}
|
||||||
<DetailsSection title="Awakening">
|
{currentValues}
|
||||||
<div class="section-content">
|
showPerpetuity={canHavePerpetuity}
|
||||||
<AwakeningSelect
|
onSave={handleSave}
|
||||||
awakenings={availableAwakenings}
|
onCancel={handleCancel}
|
||||||
value={selectedAwakening}
|
/>
|
||||||
level={awakeningLevel}
|
|
||||||
maxLevel={maxAwakeningLevel}
|
|
||||||
onAwakeningChange={(awakening) => {
|
|
||||||
selectedAwakening = awakening
|
|
||||||
}}
|
|
||||||
onLevelChange={(level) => {
|
|
||||||
awakeningLevel = level
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DetailsSection>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<DetailsSection title="Over Mastery Rings">
|
|
||||||
<div class="section-content">
|
|
||||||
<RingsSelect
|
|
||||||
{rings}
|
|
||||||
onChange={(newRings) => {
|
|
||||||
rings = newRings
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DetailsSection>
|
|
||||||
|
|
||||||
<DetailsSection title="Aetherial Mastery">
|
|
||||||
<div class="section-content">
|
|
||||||
<EarringSelect
|
|
||||||
value={earring}
|
|
||||||
element={characterElement}
|
|
||||||
onChange={(newEarring) => {
|
|
||||||
earring = newEarring
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DetailsSection>
|
|
||||||
|
|
||||||
{#if canHavePerpetuity}
|
|
||||||
<DetailsSection title="Perpetuity">
|
|
||||||
<div class="section-content">
|
|
||||||
<PerpetuityToggle
|
|
||||||
value={perpetuity}
|
|
||||||
element={elementName}
|
|
||||||
onChange={(value) => {
|
|
||||||
perpetuity = value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DetailsSection>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="edit-footer">
|
|
||||||
<Button variant="secondary" onclick={handleCancel}>Cancel</Button>
|
|
||||||
<Button variant="primary" element={elementName} elementStyle={!!elementName} onclick={handleSave}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -212,28 +82,4 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
gap: spacing.$unit-4x;
|
gap: spacing.$unit-4x;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-sections {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: spacing.$unit-3x;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-content {
|
|
||||||
padding: spacing.$unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-footer {
|
|
||||||
display: flex;
|
|
||||||
gap: spacing.$unit-2x;
|
|
||||||
padding: spacing.$unit-2x;
|
|
||||||
border-top: 1px solid var(--border-secondary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
:global(button) {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue