From b5d0b7c0e7641e81d4b5dc91fc62b90655463672 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sat, 13 Dec 2025 14:34:40 -0800 Subject: [PATCH] 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 --- .../collection/CollectionCharacterPane.svelte | 95 ++++++++++++++----- .../collection/CollectionSummonPane.svelte | 93 +++++++++++++----- .../collection/CollectionWeaponPane.svelte | 93 +++++++++++++----- 3 files changed, 209 insertions(+), 72 deletions(-) diff --git a/src/lib/components/collection/CollectionCharacterPane.svelte b/src/lib/components/collection/CollectionCharacterPane.svelte index b1a8ced9..e509954e 100644 --- a/src/lib/components/collection/CollectionCharacterPane.svelte +++ b/src/lib/components/collection/CollectionCharacterPane.svelte @@ -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(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 | 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() } }) @@ -276,12 +324,11 @@ {:else if isEditing} {:else} diff --git a/src/lib/components/collection/CollectionSummonPane.svelte b/src/lib/components/collection/CollectionSummonPane.svelte index 4cfee6e7..fbf72991 100644 --- a/src/lib/components/collection/CollectionSummonPane.svelte +++ b/src/lib/components/collection/CollectionSummonPane.svelte @@ -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(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 | 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() } }) @@ -165,11 +211,10 @@ {:else if isEditing} {:else}
diff --git a/src/lib/components/collection/CollectionWeaponPane.svelte b/src/lib/components/collection/CollectionWeaponPane.svelte index 94cd36dd..3ce6f37c 100644 --- a/src/lib/components/collection/CollectionWeaponPane.svelte +++ b/src/lib/components/collection/CollectionWeaponPane.svelte @@ -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(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 | 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() } }) @@ -250,11 +296,10 @@
{:else if isEditing} {:else}