refactor CollectionCharacterPane: use DetailSections, fix reactivity on char switch

This commit is contained in:
Justin Edmund 2025-12-02 17:19:45 -08:00
parent 0f46960de6
commit 5e5d9e93ec

View file

@ -20,8 +20,11 @@
type CharacterEditValues,
type CharacterEditUpdates
} from '$lib/components/sidebar/CharacterEditPane.svelte'
import Button from '$lib/components/ui/Button.svelte'
import Icon from '$lib/components/Icon.svelte'
import DetailRow from '$lib/components/sidebar/details/DetailRow.svelte'
import { sidebar } from '$lib/stores/sidebar.svelte'
import DetailsSection from '$lib/components/sidebar/details/DetailsSection.svelte'
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
import { getRingStat, getElementalizedEarringStat } from '$lib/utils/masteryUtils'
interface Props {
character: CollectionCharacter
@ -29,7 +32,10 @@
onClose?: () => void
}
let { character, isOwner, onClose }: Props = $props()
let { character: initialCharacter, isOwner, onClose }: Props = $props()
// Local state for the character - updated when mutation succeeds
let character = $state<CollectionCharacter>(initialCharacter)
// Tab state
let selectedTab = $state<'info' | 'collection'>('collection')
@ -37,6 +43,12 @@
// Edit mode state
let isEditing = $state(false)
// Sync local state when a different character is selected
$effect(() => {
character = initialCharacter
isEditing = false
})
// Update mutation
const updateMutation = useUpdateCollectionCharacter()
@ -45,6 +57,8 @@
// Current edit values from the collection character
const currentValues = $derived<CharacterEditValues>({
uncapLevel: character.uncapLevel,
transcendenceStep: character.transcendenceStep,
awakening: character.awakening
? {
type: character.awakening.type,
@ -79,6 +93,16 @@
// Transform updates to API format
const input: Record<string, unknown> = {}
// Handle uncap level
if (updates.uncapLevel !== undefined) {
input.uncapLevel = updates.uncapLevel
}
// Handle transcendence step
if (updates.transcendenceStep !== undefined) {
input.transcendenceStep = updates.transcendenceStep
}
// Handle awakening
if (updates.awakening !== undefined) {
if (updates.awakening === null) {
@ -108,11 +132,14 @@
input.perpetuity = updates.perpetuity
}
await updateMutation.mutateAsync({
const updatedCharacter = await updateMutation.mutateAsync({
id: character.id,
input
})
// Update local state with the response from the server
character = updatedCharacter
isEditing = false
} catch (error) {
console.error('Failed to update collection character:', error)
@ -131,20 +158,77 @@
}
}
function displayRing(ring: ExtendedMastery | null): string {
function getRingLabel(ring: ExtendedMastery | null): string {
if (!ring || ring.modifier === 0) return '—'
// TODO: Add proper ring modifier labels
return `Modifier ${ring.modifier}: ${ring.strength}`
const stat = getRingStat(ring.modifier)
return stat?.name?.en ?? '—'
}
function displayAwakening(): string {
function getRingValue(ring: ExtendedMastery | null): string {
if (!ring || ring.modifier === 0) return ''
const stat = getRingStat(ring.modifier)
if (!stat) return String(ring.strength)
return `${ring.strength}${stat.suffix}`
}
function getEarringLabel(): string {
if (!character.earring || character.earring.modifier === 0) return '—'
const stat = getElementalizedEarringStat(character.earring.modifier, characterData?.element)
return stat?.name?.en ?? '—'
}
function getEarringValue(): string {
if (!character.earring || character.earring.modifier === 0) return ''
const stat = getElementalizedEarringStat(character.earring.modifier, characterData?.element)
if (!stat) return String(character.earring.strength)
return `${character.earring.strength}${stat.suffix}`
}
function getAwakeningType(): string {
if (!character.awakening) return '—'
const name =
typeof character.awakening.type.name === 'string'
? character.awakening.type.name
: character.awakening.type.name?.en || 'Unknown'
return `${name} Lv.${character.awakening.level}`
return name
}
function getAwakeningLevel(): string {
if (!character.awakening) return '—'
return String(character.awakening.level)
}
// Check if awakening is set
const hasAwakening = $derived(character.awakening !== null)
// Check if any rings are equipped (modifier must be a positive number)
const hasRings = $derived(
(character.ring1?.modifier != null && character.ring1.modifier !== 0) ||
(character.ring2?.modifier != null && character.ring2.modifier !== 0) ||
(character.ring3?.modifier != null && character.ring3.modifier !== 0) ||
(character.ring4?.modifier != null && character.ring4.modifier !== 0)
)
// Check if earring is equipped (modifier must be a positive number)
const hasEarring = $derived(
character.earring && character.earring.modifier != null && character.earring.modifier !== 0
)
// Update sidebar header action based on current state
$effect(() => {
if (isOwner && selectedTab === 'collection' && !isEditing) {
sidebar.setAction(() => (isEditing = true), 'Edit', elementName)
} else {
sidebar.clearAction()
}
})
// Clean up sidebar action when component is destroyed
$effect(() => {
return () => {
sidebar.clearAction()
}
})
</script>
<div class="collection-character-pane">
@ -158,7 +242,13 @@
<!-- Tab navigation -->
<div class="tab-nav">
<SegmentedControl value={selectedTab} onValueChange={handleTabChange} variant="background" grow>
<SegmentedControl
value={selectedTab}
onValueChange={handleTabChange}
variant="background"
size="small"
grow
>
<Segment value="info">Info</Segment>
<Segment value="collection">My Collection</Segment>
</SegmentedControl>
@ -189,44 +279,60 @@
{:else}
<!-- My Collection view: user's customizations -->
<div class="collection-view">
<div class="customization-section">
<h4>Awakening</h4>
<p>{displayAwakening()}</p>
</div>
<DetailsSection title="General">
<DetailRow label="Uncap Level">
<UncapIndicator
type="character"
uncapLevel={character.uncapLevel}
transcendenceStage={character.transcendenceStep}
special={characterData?.special}
flb={characterData?.uncap?.flb}
ulb={characterData?.uncap?.ulb}
transcendence={characterData?.uncap?.transcendence}
/>
</DetailRow>
<DetailRow label="Perpetuity Ring" value={character.perpetuity ? 'Equipped' : '—'} />
</DetailsSection>
<div class="customization-section">
<h4>Over Mastery Rings</h4>
<div class="rings-list">
<p>Ring 1: {displayRing(character.ring1)}</p>
<p>Ring 2: {displayRing(character.ring2)}</p>
<p>Ring 3: {displayRing(character.ring3)}</p>
<p>Ring 4: {displayRing(character.ring4)}</p>
</div>
</div>
<DetailsSection title="Awakening" empty={!hasAwakening} emptyMessage="Not set">
<DetailRow label="Type" value={getAwakeningType()} />
<DetailRow label="Level" value={getAwakeningLevel()} />
</DetailsSection>
<div class="customization-section">
<h4>Aetherial Mastery</h4>
<p>{displayRing(character.earring)}</p>
</div>
<DetailsSection title="Over Mastery" empty={!hasRings} emptyMessage="No ring equipped">
{#if character.ring1?.modifier != null && character.ring1.modifier !== 0}
<DetailRow
label={getRingLabel(character.ring1)}
value={getRingValue(character.ring1)}
/>
{/if}
{#if character.ring2?.modifier != null && character.ring2.modifier !== 0}
<DetailRow
label={getRingLabel(character.ring2)}
value={getRingValue(character.ring2)}
/>
{/if}
{#if character.ring3?.modifier != null && character.ring3.modifier !== 0}
<DetailRow
label={getRingLabel(character.ring3)}
value={getRingValue(character.ring3)}
/>
{/if}
{#if character.ring4?.modifier != null && character.ring4.modifier !== 0}
<DetailRow
label={getRingLabel(character.ring4)}
value={getRingValue(character.ring4)}
/>
{/if}
</DetailsSection>
<div class="customization-section">
<h4>Perpetuity Ring</h4>
<p>{character.perpetuity ? 'Equipped' : 'Not equipped'}</p>
</div>
{#if isOwner}
<div class="edit-button-container">
<Button
variant="primary"
element={elementName}
elementStyle={!!elementName}
onclick={() => (isEditing = true)}
>
<Icon name="pencil" size={16} />
Edit Customizations
</Button>
</div>
{/if}
<DetailsSection
title="Aetherial Mastery"
empty={!hasEarring}
emptyMessage="No earring equipped"
>
<DetailRow label={getEarringLabel()} value={getEarringValue()} />
</DetailsSection>
</div>
{/if}
</div>
@ -245,7 +351,7 @@
}
.tab-nav {
padding: 0 spacing.$unit-2x spacing.$unit-2x;
padding: spacing.$unit-2x;
}
.pane-content {
@ -266,38 +372,5 @@
display: flex;
flex-direction: column;
gap: spacing.$unit-3x;
padding: 0 spacing.$unit-2x;
}
.customization-section {
h4 {
font-size: typography.$font-small;
font-weight: typography.$bold;
text-transform: uppercase;
color: var(--text-secondary, colors.$grey-50);
margin: 0 0 spacing.$unit-half;
letter-spacing: 0.5px;
}
p {
margin: 0;
color: var(--text-primary, colors.$grey-10);
}
}
.rings-list {
display: flex;
flex-direction: column;
gap: spacing.$unit-half;
p {
margin: 0;
}
}
.edit-button-container {
margin-top: spacing.$unit-2x;
padding-top: spacing.$unit-2x;
border-top: 1px solid var(--border-secondary, colors.$grey-80);
}
</style>