collection panes: add delete action, fix edit mode state

- add remove from collection with confirmation
- sidebar overflow menu for delete action
- better tracking of which item is selected to avoid state bugs
This commit is contained in:
Justin Edmund 2025-12-13 14:34:40 -08:00
parent 7558aef509
commit b5d0b7c0e7
3 changed files with 209 additions and 72 deletions

View file

@ -8,9 +8,12 @@
*
* The "My Collection" tab includes an edit mode using CharacterEditPane.
*/
import { untrack } from 'svelte'
import { onMount } from 'svelte'
import type { CollectionCharacter, ExtendedMastery } from '$lib/types/api/collection'
import { useUpdateCollectionCharacter } from '$lib/api/mutations/collection.mutations'
import {
useUpdateCollectionCharacter,
useRemoveCharacterFromCollection
} from '$lib/api/mutations/collection.mutations'
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
import ItemHeader from '$lib/components/sidebar/details/ItemHeader.svelte'
@ -38,20 +41,30 @@
// Local state for the character - updated when mutation succeeds
let character = $state<CollectionCharacter>(initialCharacter)
// Track which character we're displaying to detect when a different one is selected
let currentCharacterId = $state(initialCharacter.id)
// Tab state
let selectedTab = $state<'info' | 'collection'>('collection')
// Edit mode state
let isEditing = $state(false)
// Sync local state when a different character is selected
// Reference to the edit pane component for calling save()
let editPaneRef: ReturnType<typeof CharacterEditPane> | undefined = $state()
// Sync local state only when a DIFFERENT character is selected (not on every re-render)
$effect(() => {
character = initialCharacter
isEditing = false
if (initialCharacter.id !== currentCharacterId) {
character = initialCharacter
currentCharacterId = initialCharacter.id
isEditing = false
}
})
// Update mutation
// Mutations
const updateMutation = useUpdateCollectionCharacter()
const deleteMutation = useRemoveCharacterFromCollection()
// Derived values
const characterData = $derived(character.character)
@ -142,13 +155,57 @@
character = updatedCharacter
isEditing = false
updateActionVisibility()
} catch (error) {
console.error('Failed to update collection character:', error)
}
}
// Enter edit mode and update header action
function enterEditMode() {
isEditing = true
// Update header to show Save button
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
}
// Handle delete from collection
function handleDelete() {
if (confirm('Are you sure you want to remove this character from your collection?')) {
deleteMutation.mutate(character.id, {
onSuccess: () => {
onClose?.()
}
})
}
}
// Update action visibility when tab or edit state changes
function updateActionVisibility() {
if (isOwner && selectedTab === 'collection') {
if (isEditing) {
// Show Save button when editing, hide overflow menu
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
sidebar.clearOverflowMenu()
} else {
// Show Edit button and overflow menu when viewing
sidebar.setAction(enterEditMode, 'Edit', elementName)
sidebar.setOverflowMenu([
{
label: 'Remove from collection',
handler: handleDelete,
variant: 'danger'
}
])
}
} else {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
}
function handleCancel() {
isEditing = false
updateActionVisibility()
}
function handleTabChange(value: string) {
@ -157,6 +214,8 @@
if (isEditing) {
isEditing = false
}
// Update sidebar action visibility after state change
updateActionVisibility()
}
function getRingLabel(ring: ExtendedMastery | null): string {
@ -215,25 +274,14 @@
character.earring && character.earring.modifier != null && character.earring.modifier !== 0
)
// Update sidebar header action based on current state
$effect(() => {
// Capture reactive dependencies we want to track
const shouldShowEdit = isOwner && selectedTab === 'collection' && !isEditing
const element = elementName
// Use untrack to avoid infinite loop when sidebar.setAction mutates pane state
untrack(() => {
if (shouldShowEdit) {
sidebar.setAction(() => (isEditing = true), 'Edit', element)
} else {
sidebar.clearAction()
}
})
})
// Set up sidebar action on mount and clean up on destroy
onMount(() => {
// Initial setup - show Edit button if on collection tab and owner
updateActionVisibility()
// Clean up sidebar action when component is destroyed
$effect(() => {
return () => {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
})
</script>
@ -276,12 +324,11 @@
{:else if isEditing}
<!-- Edit mode -->
<CharacterEditPane
bind:this={editPaneRef}
{characterData}
{currentValues}
showPerpetuity={true}
onSave={handleSave}
onCancel={handleCancel}
saving={updateMutation.isPending}
/>
{:else}
<!-- My Collection view: user's customizations -->

View file

@ -8,9 +8,12 @@
*
* The "My Collection" tab includes an edit mode using SummonEditPane.
*/
import { untrack } from 'svelte'
import { onMount } from 'svelte'
import type { CollectionSummon } from '$lib/types/api/collection'
import { useUpdateCollectionSummon } from '$lib/api/mutations/collection.mutations'
import {
useUpdateCollectionSummon,
useRemoveSummonFromCollection
} from '$lib/api/mutations/collection.mutations'
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
import ItemHeader from '$lib/components/sidebar/details/ItemHeader.svelte'
@ -36,20 +39,30 @@
// Local state for the summon - updated when mutation succeeds
let summon = $state<CollectionSummon>(initialSummon)
// Track which summon we're displaying to detect when a different one is selected
let currentSummonId = $state(initialSummon.id)
// Tab state
let selectedTab = $state<'info' | 'collection'>('collection')
// Edit mode state
let isEditing = $state(false)
// Sync local state when a different summon is selected
// Reference to the edit pane component for calling save()
let editPaneRef: ReturnType<typeof SummonEditPane> | undefined = $state()
// Sync local state only when a DIFFERENT summon is selected (not on every re-render)
$effect(() => {
summon = initialSummon
isEditing = false
if (initialSummon.id !== currentSummonId) {
summon = initialSummon
currentSummonId = initialSummon.id
isEditing = false
}
})
// Update mutation
// Mutations
const updateMutation = useUpdateCollectionSummon()
const deleteMutation = useRemoveSummonFromCollection()
// Derived values
const summonData = $derived(summon.summon)
@ -92,13 +105,57 @@
summon = updatedSummon
isEditing = false
updateActionVisibility()
} catch (error) {
console.error('Failed to update collection summon:', error)
}
}
// Enter edit mode and update header action
function enterEditMode() {
isEditing = true
// Update header to show Save button
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
}
// Handle delete from collection
function handleDelete() {
if (confirm('Are you sure you want to remove this summon from your collection?')) {
deleteMutation.mutate(summon.id, {
onSuccess: () => {
onClose?.()
}
})
}
}
// Update action visibility when tab or edit state changes
function updateActionVisibility() {
if (isOwner && selectedTab === 'collection') {
if (isEditing) {
// Show Save button when editing, hide overflow menu
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
sidebar.clearOverflowMenu()
} else {
// Show Edit button and overflow menu when viewing
sidebar.setAction(enterEditMode, 'Edit', elementName)
sidebar.setOverflowMenu([
{
label: 'Remove from collection',
handler: handleDelete,
variant: 'danger'
}
])
}
} else {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
}
function handleCancel() {
isEditing = false
updateActionVisibility()
}
function handleTabChange(value: string) {
@ -106,27 +163,16 @@
if (isEditing) {
isEditing = false
}
updateActionVisibility()
}
// Update sidebar header action
$effect(() => {
// Capture reactive dependencies we want to track
const shouldShowEdit = isOwner && selectedTab === 'collection' && !isEditing
const element = elementName
// Use untrack to avoid infinite loop when sidebar.setAction mutates pane state
untrack(() => {
if (shouldShowEdit) {
sidebar.setAction(() => (isEditing = true), 'Edit', element)
} else {
sidebar.clearAction()
}
})
})
// Set up sidebar action on mount and clean up on destroy
onMount(() => {
updateActionVisibility()
// Clean up sidebar action when component is destroyed
$effect(() => {
return () => {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
})
</script>
@ -165,11 +211,10 @@
</div>
{:else if isEditing}
<SummonEditPane
bind:this={editPaneRef}
{summonData}
{currentValues}
onSave={handleSave}
onCancel={handleCancel}
saving={updateMutation.isPending}
/>
{:else}
<div class="collection-view">

View file

@ -8,10 +8,13 @@
*
* The "My Collection" tab includes an edit mode using WeaponEditPane.
*/
import { untrack } from 'svelte'
import { onMount } from 'svelte'
import type { CollectionWeapon } from '$lib/types/api/collection'
import type { SimpleAxSkill } from '$lib/types/api/entities'
import { useUpdateCollectionWeapon } from '$lib/api/mutations/collection.mutations'
import {
useUpdateCollectionWeapon,
useRemoveWeaponFromCollection
} from '$lib/api/mutations/collection.mutations'
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
import ItemHeader from '$lib/components/sidebar/details/ItemHeader.svelte'
@ -39,20 +42,30 @@
// Local state for the weapon - updated when mutation succeeds
let weapon = $state<CollectionWeapon>(initialWeapon)
// Track which weapon we're displaying to detect when a different one is selected
let currentWeaponId = $state(initialWeapon.id)
// Tab state
let selectedTab = $state<'info' | 'collection'>('collection')
// Edit mode state
let isEditing = $state(false)
// Sync local state when a different weapon is selected
// Reference to the edit pane component for calling save()
let editPaneRef: ReturnType<typeof WeaponEditPane> | undefined = $state()
// Sync local state only when a DIFFERENT weapon is selected (not on every re-render)
$effect(() => {
weapon = initialWeapon
isEditing = false
if (initialWeapon.id !== currentWeaponId) {
weapon = initialWeapon
currentWeaponId = initialWeapon.id
isEditing = false
}
})
// Update mutation
// Mutations
const updateMutation = useUpdateCollectionWeapon()
const deleteMutation = useRemoveWeaponFromCollection()
// Derived values
const weaponData = $derived(weapon.weapon)
@ -148,13 +161,57 @@
weapon = updatedWeapon
isEditing = false
updateActionVisibility()
} catch (error) {
console.error('Failed to update collection weapon:', error)
}
}
// Enter edit mode and update header action
function enterEditMode() {
isEditing = true
// Update header to show Save button
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
}
// Handle delete from collection
function handleDelete() {
if (confirm('Are you sure you want to remove this weapon from your collection?')) {
deleteMutation.mutate(weapon.id, {
onSuccess: () => {
onClose?.()
}
})
}
}
// Update action visibility when tab or edit state changes
function updateActionVisibility() {
if (isOwner && selectedTab === 'collection') {
if (isEditing) {
// Show Save button when editing, hide overflow menu
sidebar.setAction(() => editPaneRef?.save(), 'Save', elementName)
sidebar.clearOverflowMenu()
} else {
// Show Edit button and overflow menu when viewing
sidebar.setAction(enterEditMode, 'Edit', elementName)
sidebar.setOverflowMenu([
{
label: 'Remove from collection',
handler: handleDelete,
variant: 'danger'
}
])
}
} else {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
}
function handleCancel() {
isEditing = false
updateActionVisibility()
}
function handleTabChange(value: string) {
@ -162,6 +219,7 @@
if (isEditing) {
isEditing = false
}
updateActionVisibility()
}
function getAwakeningType(): string {
@ -192,25 +250,13 @@
const hasAxSkills = $derived((weapon.ax?.length ?? 0) > 0 && weapon.ax?.some(ax => ax.modifier >= 0))
const canChangeElement = $derived(weaponData?.element === 0)
// Update sidebar header action
$effect(() => {
// Capture reactive dependencies we want to track
const shouldShowEdit = isOwner && selectedTab === 'collection' && !isEditing
const element = elementName
// Use untrack to avoid infinite loop when sidebar.setAction mutates pane state
untrack(() => {
if (shouldShowEdit) {
sidebar.setAction(() => (isEditing = true), 'Edit', element)
} else {
sidebar.clearAction()
}
})
})
// Set up sidebar action on mount and clean up on destroy
onMount(() => {
updateActionVisibility()
// Clean up sidebar action when component is destroyed
$effect(() => {
return () => {
sidebar.clearAction()
sidebar.clearOverflowMenu()
}
})
</script>
@ -250,11 +296,10 @@
</div>
{:else if isEditing}
<WeaponEditPane
bind:this={editPaneRef}
{weaponData}
{currentValues}
onSave={handleSave}
onCancel={handleCancel}
saving={updateMutation.isPending}
/>
{:else}
<div class="collection-view">