From 47885b1429387ba19e4eb95df6554e06f4d49bc3 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 30 Nov 2025 20:06:26 -0800 Subject: [PATCH] sidebar: wire up edit sidebars in openDetailsSidebar feature --- .../details/openDetailsSidebar.svelte.ts | 146 ++++++++++++++++++ src/lib/stores/sidebar.svelte.ts | 50 +++++- 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/lib/features/details/openDetailsSidebar.svelte.ts b/src/lib/features/details/openDetailsSidebar.svelte.ts index 0191934b..8d6ee6f6 100644 --- a/src/lib/features/details/openDetailsSidebar.svelte.ts +++ b/src/lib/features/details/openDetailsSidebar.svelte.ts @@ -1,12 +1,43 @@ import { sidebar } from '$lib/stores/sidebar.svelte' +import { partyStore } from '$lib/stores/partyStore.svelte' import DetailsSidebar from '$lib/components/sidebar/DetailsSidebar.svelte' +import EditWeaponSidebar from '$lib/components/sidebar/EditWeaponSidebar.svelte' +import EditCharacterSidebar from '$lib/components/sidebar/EditCharacterSidebar.svelte' import type { GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party' +import { canWeaponBeModified, canCharacterBeModified } from '$lib/utils/modificationDetector' interface DetailsSidebarOptions { type: 'weapon' | 'character' | 'summon' item: GridCharacter | GridWeapon | GridSummon } +type ElementName = 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' + +const ELEMENT_MAP: Record = { + 1: 'wind', + 2: 'fire', + 3: 'water', + 4: 'earth', + 5: 'dark', + 6: 'light' +} + +function getItemElement(type: 'weapon' | 'character' | 'summon', item: GridCharacter | GridWeapon | GridSummon): ElementName | undefined { + let elementId: number | undefined + + if (type === 'character') { + elementId = (item as GridCharacter).character?.element + } else if (type === 'weapon') { + const weapon = item as GridWeapon + // Use grid weapon element if set, otherwise canonical weapon element + elementId = weapon.element || weapon.weapon?.element + } else if (type === 'summon') { + elementId = (item as GridSummon).summon?.element + } + + return elementId ? ELEMENT_MAP[elementId] : undefined +} + export function openDetailsSidebar(options: DetailsSidebarOptions) { const { type, item } = options @@ -23,11 +54,126 @@ export function openDetailsSidebar(options: DetailsSidebarOptions) { itemName = getName(summon) } + // Check if this item can be edited + const canEditWeapon = type === 'weapon' && canWeaponBeModified(item as GridWeapon) + const canEditCharacter = type === 'character' && canCharacterBeModified(item as GridCharacter) + const canEdit = canEditWeapon || canEditCharacter + + // Create edit handler for editable items + const onsave = canEdit + ? () => { + if (canEditWeapon) { + openWeaponEditSidebar(item as GridWeapon) + } else if (canEditCharacter) { + openCharacterEditSidebar(item as GridCharacter) + } + } + : undefined + + // Get the element for styling + const element = getItemElement(type, item) + // Open the sidebar with the details component const title = itemName !== 'Details' ? itemName : `${type.charAt(0).toUpperCase() + type.slice(1)} Details` sidebar.openWithComponent(title, DetailsSidebar, { type, item + }, { + onsave, + saveLabel: 'Edit', + element + }) +} + +export function openWeaponEditSidebar(weapon: GridWeapon) { + const weaponName = getName(weapon.weapon) + const title = weaponName !== 'Details' ? weaponName : 'Edit Weapon' + + // Get element for styling + const element = getItemElement('weapon', weapon) + + // Keep track of the current weapon state for going back to details + let currentWeapon = weapon + + // Handler to go back to details view + const goBackToDetails = () => { + // Get the updated weapon from the store if available + const updated = partyStore.getWeapon(weapon.id) + openDetailsSidebar({ type: 'weapon', item: updated ?? currentWeapon }) + } + + // Handler for save button - saves updates via partyStore + const handleSave = async (updates: Partial) => { + if (!weapon.id) { + console.error('Cannot save weapon without ID') + goBackToDetails() + return + } + + try { + const updated = await partyStore.updateWeapon(String(weapon.id), updates) + currentWeapon = updated + goBackToDetails() + } catch (error) { + console.error('Failed to save weapon:', error) + // Still go back on error - the optimistic update will be visible + goBackToDetails() + } + } + + sidebar.openWithComponent(title, EditWeaponSidebar, { + weapon, + onSave: handleSave, + onCancel: goBackToDetails + }, { + element, + onback: goBackToDetails + }) +} + +export function openCharacterEditSidebar(character: GridCharacter) { + const characterName = getName(character.character) + const title = characterName !== 'Details' ? characterName : 'Edit Character' + + // Get element for styling + const element = getItemElement('character', character) + + // Keep track of the current character state for going back to details + let currentCharacter = character + + // Handler to go back to details view + const goBackToDetails = () => { + // Get the updated character from the store if available + const updated = partyStore.getCharacter(character.id) + openDetailsSidebar({ type: 'character', item: updated ?? currentCharacter }) + } + + // Handler for save button - saves updates via partyStore + const handleSave = async (updates: Partial) => { + if (!character.id) { + console.error('Cannot save character without ID') + goBackToDetails() + return + } + + try { + const updated = await partyStore.updateCharacter(String(character.id), updates) + currentCharacter = updated + goBackToDetails() + } catch (error) { + console.error('Failed to save character:', error) + // Still go back on error - the optimistic update will be visible + goBackToDetails() + } + } + + sidebar.openWithComponent(title, EditCharacterSidebar, { + character, + onSave: handleSave, + onCancel: goBackToDetails + }, { + element, + onback: goBackToDetails }) } diff --git a/src/lib/stores/sidebar.svelte.ts b/src/lib/stores/sidebar.svelte.ts index aa05d6b7..27c19e30 100644 --- a/src/lib/stores/sidebar.svelte.ts +++ b/src/lib/stores/sidebar.svelte.ts @@ -11,6 +11,18 @@ interface SidebarState { componentProps: Record | undefined scrollable: boolean activeItemId: string | undefined + onsave: (() => void) | undefined + saveLabel: string | undefined + element: 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' | undefined + onback: (() => void) | undefined +} + +interface OpenWithComponentOptions { + scrollable?: boolean + onsave?: () => void + saveLabel?: string + element?: 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' + onback?: () => void } class SidebarStore { @@ -21,7 +33,11 @@ class SidebarStore { component: undefined, componentProps: undefined, scrollable: true, - activeItemId: undefined + activeItemId: undefined, + onsave: undefined, + saveLabel: undefined, + element: undefined, + onback: undefined }) open(title?: string, content?: Snippet, scrollable = true) { @@ -37,14 +53,22 @@ class SidebarStore { title: string, component: Component, props?: Record, - scrollable = true + options?: OpenWithComponentOptions | boolean ) { + // Handle backward compatibility where 4th param was scrollable boolean + const opts: OpenWithComponentOptions = + typeof options === 'boolean' ? { scrollable: options } : options ?? {} + this.state.open = true this.state.title = title this.state.component = component this.state.componentProps = props this.state.content = undefined - this.state.scrollable = scrollable + this.state.scrollable = opts.scrollable ?? true + this.state.onsave = opts.onsave + this.state.saveLabel = opts.saveLabel + this.state.element = opts.element + this.state.onback = opts.onback // Extract and store the item ID if it's a details sidebar if (props?.item?.id) { this.state.activeItemId = String(props.item.id) @@ -60,6 +84,10 @@ class SidebarStore { this.state.content = undefined this.state.component = undefined this.state.componentProps = undefined + this.state.onsave = undefined + this.state.saveLabel = undefined + this.state.element = undefined + this.state.onback = undefined }, 300) } @@ -98,6 +126,22 @@ class SidebarStore { get activeItemId() { return this.state.activeItemId } + + get onsave() { + return this.state.onsave + } + + get saveLabel() { + return this.state.saveLabel + } + + get element() { + return this.state.element + } + + get onback() { + return this.state.onback + } } export const sidebar = new SidebarStore()