refactor CollectionCharacterPane: use DetailSections, fix reactivity on char switch
This commit is contained in:
parent
0f46960de6
commit
5e5d9e93ec
1 changed files with 152 additions and 79 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue