sidebar: wire up edit sidebars in openDetailsSidebar feature
This commit is contained in:
parent
8ac9dea2d3
commit
47885b1429
2 changed files with 193 additions and 3 deletions
|
|
@ -1,12 +1,43 @@
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
|
import { partyStore } from '$lib/stores/partyStore.svelte'
|
||||||
import DetailsSidebar from '$lib/components/sidebar/DetailsSidebar.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 type { GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
||||||
|
import { canWeaponBeModified, canCharacterBeModified } from '$lib/utils/modificationDetector'
|
||||||
|
|
||||||
interface DetailsSidebarOptions {
|
interface DetailsSidebarOptions {
|
||||||
type: 'weapon' | 'character' | 'summon'
|
type: 'weapon' | 'character' | 'summon'
|
||||||
item: GridCharacter | GridWeapon | GridSummon
|
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) {
|
export function openDetailsSidebar(options: DetailsSidebarOptions) {
|
||||||
const { type, item } = options
|
const { type, item } = options
|
||||||
|
|
||||||
|
|
@ -23,11 +54,126 @@ export function openDetailsSidebar(options: DetailsSidebarOptions) {
|
||||||
itemName = getName(summon)
|
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
|
// Open the sidebar with the details component
|
||||||
const title = itemName !== 'Details' ? itemName : `${type.charAt(0).toUpperCase() + type.slice(1)} Details`
|
const title = itemName !== 'Details' ? itemName : `${type.charAt(0).toUpperCase() + type.slice(1)} Details`
|
||||||
sidebar.openWithComponent(title, DetailsSidebar, {
|
sidebar.openWithComponent(title, DetailsSidebar, {
|
||||||
type,
|
type,
|
||||||
item
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,18 @@ interface SidebarState {
|
||||||
componentProps: Record<string, any> | undefined
|
componentProps: Record<string, any> | undefined
|
||||||
scrollable: boolean
|
scrollable: boolean
|
||||||
activeItemId: string | undefined
|
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 {
|
class SidebarStore {
|
||||||
|
|
@ -21,7 +33,11 @@ class SidebarStore {
|
||||||
component: undefined,
|
component: undefined,
|
||||||
componentProps: undefined,
|
componentProps: undefined,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
activeItemId: undefined
|
activeItemId: undefined,
|
||||||
|
onsave: undefined,
|
||||||
|
saveLabel: undefined,
|
||||||
|
element: undefined,
|
||||||
|
onback: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
open(title?: string, content?: Snippet, scrollable = true) {
|
open(title?: string, content?: Snippet, scrollable = true) {
|
||||||
|
|
@ -37,14 +53,22 @@ class SidebarStore {
|
||||||
title: string,
|
title: string,
|
||||||
component: Component<any, any, any>,
|
component: Component<any, any, any>,
|
||||||
props?: Record<string, 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.open = true
|
||||||
this.state.title = title
|
this.state.title = title
|
||||||
this.state.component = component
|
this.state.component = component
|
||||||
this.state.componentProps = props
|
this.state.componentProps = props
|
||||||
this.state.content = undefined
|
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
|
// Extract and store the item ID if it's a details sidebar
|
||||||
if (props?.item?.id) {
|
if (props?.item?.id) {
|
||||||
this.state.activeItemId = String(props.item.id)
|
this.state.activeItemId = String(props.item.id)
|
||||||
|
|
@ -60,6 +84,10 @@ class SidebarStore {
|
||||||
this.state.content = undefined
|
this.state.content = undefined
|
||||||
this.state.component = undefined
|
this.state.component = undefined
|
||||||
this.state.componentProps = undefined
|
this.state.componentProps = undefined
|
||||||
|
this.state.onsave = undefined
|
||||||
|
this.state.saveLabel = undefined
|
||||||
|
this.state.element = undefined
|
||||||
|
this.state.onback = undefined
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +126,22 @@ class SidebarStore {
|
||||||
get activeItemId() {
|
get activeItemId() {
|
||||||
return this.state.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()
|
export const sidebar = new SidebarStore()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue