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 CharacterEditValues,
|
||||||
type CharacterEditUpdates
|
type CharacterEditUpdates
|
||||||
} from '$lib/components/sidebar/CharacterEditPane.svelte'
|
} from '$lib/components/sidebar/CharacterEditPane.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import DetailRow from '$lib/components/sidebar/details/DetailRow.svelte'
|
||||||
import Icon from '$lib/components/Icon.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 {
|
interface Props {
|
||||||
character: CollectionCharacter
|
character: CollectionCharacter
|
||||||
|
|
@ -29,7 +32,10 @@
|
||||||
onClose?: () => void
|
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
|
// Tab state
|
||||||
let selectedTab = $state<'info' | 'collection'>('collection')
|
let selectedTab = $state<'info' | 'collection'>('collection')
|
||||||
|
|
@ -37,6 +43,12 @@
|
||||||
// Edit mode state
|
// Edit mode state
|
||||||
let isEditing = $state(false)
|
let isEditing = $state(false)
|
||||||
|
|
||||||
|
// Sync local state when a different character is selected
|
||||||
|
$effect(() => {
|
||||||
|
character = initialCharacter
|
||||||
|
isEditing = false
|
||||||
|
})
|
||||||
|
|
||||||
// Update mutation
|
// Update mutation
|
||||||
const updateMutation = useUpdateCollectionCharacter()
|
const updateMutation = useUpdateCollectionCharacter()
|
||||||
|
|
||||||
|
|
@ -45,6 +57,8 @@
|
||||||
|
|
||||||
// Current edit values from the collection character
|
// Current edit values from the collection character
|
||||||
const currentValues = $derived<CharacterEditValues>({
|
const currentValues = $derived<CharacterEditValues>({
|
||||||
|
uncapLevel: character.uncapLevel,
|
||||||
|
transcendenceStep: character.transcendenceStep,
|
||||||
awakening: character.awakening
|
awakening: character.awakening
|
||||||
? {
|
? {
|
||||||
type: character.awakening.type,
|
type: character.awakening.type,
|
||||||
|
|
@ -79,6 +93,16 @@
|
||||||
// Transform updates to API format
|
// Transform updates to API format
|
||||||
const input: Record<string, unknown> = {}
|
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
|
// Handle awakening
|
||||||
if (updates.awakening !== undefined) {
|
if (updates.awakening !== undefined) {
|
||||||
if (updates.awakening === null) {
|
if (updates.awakening === null) {
|
||||||
|
|
@ -108,11 +132,14 @@
|
||||||
input.perpetuity = updates.perpetuity
|
input.perpetuity = updates.perpetuity
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateMutation.mutateAsync({
|
const updatedCharacter = await updateMutation.mutateAsync({
|
||||||
id: character.id,
|
id: character.id,
|
||||||
input
|
input
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Update local state with the response from the server
|
||||||
|
character = updatedCharacter
|
||||||
|
|
||||||
isEditing = false
|
isEditing = false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update collection character:', 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 '—'
|
if (!ring || ring.modifier === 0) return '—'
|
||||||
// TODO: Add proper ring modifier labels
|
const stat = getRingStat(ring.modifier)
|
||||||
return `Modifier ${ring.modifier}: ${ring.strength}`
|
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 '—'
|
if (!character.awakening) return '—'
|
||||||
const name =
|
const name =
|
||||||
typeof character.awakening.type.name === 'string'
|
typeof character.awakening.type.name === 'string'
|
||||||
? character.awakening.type.name
|
? character.awakening.type.name
|
||||||
: character.awakening.type.name?.en || 'Unknown'
|
: 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>
|
</script>
|
||||||
|
|
||||||
<div class="collection-character-pane">
|
<div class="collection-character-pane">
|
||||||
|
|
@ -158,7 +242,13 @@
|
||||||
|
|
||||||
<!-- Tab navigation -->
|
<!-- Tab navigation -->
|
||||||
<div class="tab-nav">
|
<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="info">Info</Segment>
|
||||||
<Segment value="collection">My Collection</Segment>
|
<Segment value="collection">My Collection</Segment>
|
||||||
</SegmentedControl>
|
</SegmentedControl>
|
||||||
|
|
@ -189,44 +279,60 @@
|
||||||
{:else}
|
{:else}
|
||||||
<!-- My Collection view: user's customizations -->
|
<!-- My Collection view: user's customizations -->
|
||||||
<div class="collection-view">
|
<div class="collection-view">
|
||||||
<div class="customization-section">
|
<DetailsSection title="General">
|
||||||
<h4>Awakening</h4>
|
<DetailRow label="Uncap Level">
|
||||||
<p>{displayAwakening()}</p>
|
<UncapIndicator
|
||||||
</div>
|
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">
|
<DetailsSection title="Awakening" empty={!hasAwakening} emptyMessage="Not set">
|
||||||
<h4>Over Mastery Rings</h4>
|
<DetailRow label="Type" value={getAwakeningType()} />
|
||||||
<div class="rings-list">
|
<DetailRow label="Level" value={getAwakeningLevel()} />
|
||||||
<p>Ring 1: {displayRing(character.ring1)}</p>
|
</DetailsSection>
|
||||||
<p>Ring 2: {displayRing(character.ring2)}</p>
|
|
||||||
<p>Ring 3: {displayRing(character.ring3)}</p>
|
|
||||||
<p>Ring 4: {displayRing(character.ring4)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="customization-section">
|
<DetailsSection title="Over Mastery" empty={!hasRings} emptyMessage="No ring equipped">
|
||||||
<h4>Aetherial Mastery</h4>
|
{#if character.ring1?.modifier != null && character.ring1.modifier !== 0}
|
||||||
<p>{displayRing(character.earring)}</p>
|
<DetailRow
|
||||||
</div>
|
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">
|
<DetailsSection
|
||||||
<h4>Perpetuity Ring</h4>
|
title="Aetherial Mastery"
|
||||||
<p>{character.perpetuity ? 'Equipped' : 'Not equipped'}</p>
|
empty={!hasEarring}
|
||||||
</div>
|
emptyMessage="No earring equipped"
|
||||||
|
>
|
||||||
{#if isOwner}
|
<DetailRow label={getEarringLabel()} value={getEarringValue()} />
|
||||||
<div class="edit-button-container">
|
</DetailsSection>
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
element={elementName}
|
|
||||||
elementStyle={!!elementName}
|
|
||||||
onclick={() => (isEditing = true)}
|
|
||||||
>
|
|
||||||
<Icon name="pencil" size={16} />
|
|
||||||
Edit Customizations
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -245,7 +351,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-nav {
|
.tab-nav {
|
||||||
padding: 0 spacing.$unit-2x spacing.$unit-2x;
|
padding: spacing.$unit-2x;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane-content {
|
.pane-content {
|
||||||
|
|
@ -266,38 +372,5 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: spacing.$unit-3x;
|
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>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue