sidebar: wire up edit sidebars in openDetailsSidebar feature

This commit is contained in:
Justin Edmund 2025-11-30 20:06:26 -08:00
parent 8ac9dea2d3
commit 47885b1429
2 changed files with 193 additions and 3 deletions

View file

@ -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<number, ElementName> = {
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<GridWeapon>) => {
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<GridCharacter>) => {
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
})
}

View file

@ -11,6 +11,18 @@ interface SidebarState {
componentProps: Record<string, any> | 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<any, any, any>,
props?: Record<string, any>,
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()