From e26b5c2e207bea4950cfca25fedcbc46ef4eac7c Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 15 Sep 2025 21:24:49 -0700 Subject: [PATCH] add context menus to units and party --- src/lib/components/party/Party.svelte | 224 +++++++++++++++--- src/lib/components/units/CharacterUnit.svelte | 105 ++++++-- src/lib/components/units/SummonUnit.svelte | 126 +++++++--- src/lib/components/units/WeaponUnit.svelte | 116 ++++++--- 4 files changed, 459 insertions(+), 112 deletions(-) diff --git a/src/lib/components/party/Party.svelte b/src/lib/components/party/Party.svelte index bc0597ec..d33a0012 100644 --- a/src/lib/components/party/Party.svelte +++ b/src/lib/components/party/Party.svelte @@ -10,6 +10,7 @@ import CharacterGrid from '$lib/components/grids/CharacterGrid.svelte' import SearchSidebar from '$lib/components/panels/SearchSidebar.svelte' import type { SearchResult } from '$lib/api/resources/search' + import Dialog from '$lib/components/ui/Dialog.svelte' interface Props { party?: Party @@ -42,6 +43,8 @@ let pickerOpen = $state(false) let pickerTitle = $state('Search') let selectedSlot = $state(0) + let editDialogOpen = $state(false) + let editingTitle = $state('') // Services const partyService = new PartyService(fetch) @@ -93,23 +96,49 @@ function selectTab(key: typeof tabs[number]['key']) { activeTab = key } - + + // Edit dialog functions + function openEditDialog() { + if (!canEdit()) return + editingTitle = party.name || '' + editDialogOpen = true + } + + async function savePartyTitle() { + if (!canEdit()) return + + try { + loading = true + error = null + + // Update party title via API + const updated = await updatePartyDetails({ name: editingTitle }) + if (updated) { + party = updated + editDialogOpen = false + } + } catch (err: any) { + error = err.message || 'Failed to update party title' + } finally { + loading = false + } + } + // Party operations async function updatePartyDetails(updates: Partial) { - if (!canEdit()) return - + if (!canEdit()) return null + loading = true error = null - + try { - const updated = await partyService.update( - party.id, - updates, - editKey || undefined - ) + // Use apiClient for client-side updates (handles edit keys automatically) + const updated = await apiClient.updateParty(party.id, updates) party = updated + return updated } catch (err: any) { error = err.message || 'Failed to update party' + return null } finally { loading = false } @@ -263,22 +292,37 @@ // Create client-side wrappers for grid operations using API client const clientGridService = { async removeWeapon(partyId: string, gridWeaponId: string, _editKey?: string) { - await apiClient.removeWeapon(partyId, gridWeaponId) - // Reload party data - const updated = await partyService.getByShortcode(party.shortcode) - return updated + try { + await apiClient.removeWeapon(partyId, gridWeaponId) + // Reload party data + const updated = await partyService.getByShortcode(party.shortcode) + return updated + } catch (err) { + console.error('Failed to remove weapon:', err) + throw err + } }, async removeSummon(partyId: string, gridSummonId: string, _editKey?: string) { - await apiClient.removeSummon(partyId, gridSummonId) - // Reload party data - const updated = await partyService.getByShortcode(party.shortcode) - return updated + try { + await apiClient.removeSummon(partyId, gridSummonId) + // Reload party data + const updated = await partyService.getByShortcode(party.shortcode) + return updated + } catch (err) { + console.error('Failed to remove summon:', err) + throw err + } }, async removeCharacter(partyId: string, gridCharacterId: string, _editKey?: string) { - await apiClient.removeCharacter(partyId, gridCharacterId) - // Reload party data - const updated = await partyService.getByShortcode(party.shortcode) - return updated + try { + await apiClient.removeCharacter(partyId, gridCharacterId) + // Reload party data + const updated = await partyService.getByShortcode(party.shortcode) + return updated + } catch (err) { + console.error('Failed to remove character:', err) + throw err + } } } @@ -314,23 +358,34 @@

{party.description}

{/if} - +
+ {#if canEdit()} + + {/if} + {#if authUserId} - {/if} - -
+ + + {#snippet children()} +
+ + +
+ {/snippet} + + {#snippet footer()} + + + {/snippet} +
+ diff --git a/src/lib/components/units/CharacterUnit.svelte b/src/lib/components/units/CharacterUnit.svelte index 8eb5dc0c..765d355b 100644 --- a/src/lib/components/units/CharacterUnit.svelte +++ b/src/lib/components/units/CharacterUnit.svelte @@ -3,6 +3,8 @@ import type { Party } from '$lib/types/api/party' import { getContext } from 'svelte' import Icon from '$lib/components/Icon.svelte' + import ContextMenu from '$lib/components/ui/ContextMenu.svelte' + import { ContextMenu as ContextMenuBase } from 'bits-ui' interface Props { item?: GridCharacter @@ -55,35 +57,92 @@ async function remove() { if (!item?.id) return - const party = ctx.getParty() - const editKey = ctx.getEditKey() - const updated = await ctx.services.gridService.removeCharacter(party.id, item.id as any, editKey || undefined) - ctx.updateParty(updated) + try { + const party = ctx.getParty() + const editKey = ctx.getEditKey() + const updated = await ctx.services.gridService.removeCharacter(party.id, item.id as any, editKey || undefined) + if (updated) { + ctx.updateParty(updated) + } + } catch (err) { + console.error('Error removing character:', err) + } + } + + function viewDetails() { + // TODO: Implement view details modal + console.log('View details for:', item) + } + + function replace() { + if (ctx?.openPicker) { + ctx.openPicker({ type: 'character', position, item }) + } }
- {#key (item ? (item as any).id ?? position : `empty-${position}`)} -
ctx?.openPicker && ctx.openPicker({ type: 'character', position, item })} - > - {item - {#if !item && ctx?.canEdit()} - - - - {/if} - {#if ctx.canEdit() && item?.id} -
- -
- {/if} -
- {/key} + {#if item} + + {#snippet children()} + {#key (item as any).id ?? position} +
ctx?.canEdit() && replace()} + > + {displayName(item?.character + {#if ctx?.canEdit() && item?.id} +
+ +
+ {/if} +
+ {/key} + {/snippet} + + {#snippet menu()} + + View Details + + {#if ctx?.canEdit()} + + Replace + + + + Remove + + {/if} + {/snippet} +
+ {:else} + {#key `empty-${position}`} +
ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'character', position, item })} + > + + {#if ctx?.canEdit()} + + + + {/if} +
+ {/key} + {/if}
{item ? displayName(item?.character || (item as any)?.object) : ''}
diff --git a/src/lib/components/units/SummonUnit.svelte b/src/lib/components/units/SummonUnit.svelte index 5c4643bc..3cf25ad6 100644 --- a/src/lib/components/units/SummonUnit.svelte +++ b/src/lib/components/units/SummonUnit.svelte @@ -3,6 +3,8 @@ import type { Party } from '$lib/types/api/party' import { getContext } from 'svelte' import Icon from '$lib/components/Icon.svelte' + import ContextMenu from '$lib/components/ui/ContextMenu.svelte' + import { ContextMenu as ContextMenuBase } from 'bits-ui' interface Props { item?: GridSummon @@ -49,44 +51,104 @@ async function remove() { if (!item?.id) return - const party = ctx.getParty() - const editKey = ctx.getEditKey() - const updated = await ctx.services.gridService.removeSummon(party.id, item.id as any, editKey || undefined) - ctx.updateParty(updated) + try { + const party = ctx.getParty() + const editKey = ctx.getEditKey() + const updated = await ctx.services.gridService.removeSummon(party.id, item.id as any, editKey || undefined) + if (updated) { + ctx.updateParty(updated) + } + } catch (err) { + console.error('Error removing summon:', err) + } + } + + function viewDetails() { + // TODO: Implement view details modal + console.log('View details for:', item) + } + + function replace() { + if (ctx?.openPicker) { + ctx.openPicker({ type: 'summon', position, item }) + } }
- {#key (item ? (item as any).id ?? position : `empty-${position}`)} -
ctx?.openPicker && ctx.openPicker({ type: 'summon', position, item })} - > - {item - {#if !item && ctx?.canEdit()} - - - - {/if} - {#if ctx.canEdit() && item?.id} -
- -
- {/if} - {#if item?.main || position === -1} - Main - {/if} - {#if item?.friend || position === 6} - Friend - {/if} -
- {/key} + {#if item} + + {#snippet children()} + {#key (item as any).id ?? position} +
ctx?.canEdit() && replace()} + > + {displayName(item?.summon + {#if ctx?.canEdit() && item?.id} +
+ +
+ {/if} + {#if item?.main || position === -1} + Main + {/if} + {#if item?.friend || position === 6} + Friend + {/if} +
+ {/key} + {/snippet} + + {#snippet menu()} + + View Details + + {#if ctx?.canEdit()} + + Replace + + + + Remove + + {/if} + {/snippet} +
+ {:else} + {#key `empty-${position}`} +
ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'summon', position, item })} + > + + {#if ctx?.canEdit()} + + + + {/if} +
+ {/key} + {/if}
{item ? displayName(item?.summon || (item as any)?.object) : ''}
diff --git a/src/lib/components/units/WeaponUnit.svelte b/src/lib/components/units/WeaponUnit.svelte index 43a2aa05..fdb3aac9 100644 --- a/src/lib/components/units/WeaponUnit.svelte +++ b/src/lib/components/units/WeaponUnit.svelte @@ -3,6 +3,8 @@ import type { Party } from '$lib/types/api/party' import { getContext } from 'svelte' import Icon from '$lib/components/Icon.svelte' + import ContextMenu from '$lib/components/ui/ContextMenu.svelte' + import { ContextMenu as ContextMenuBase } from 'bits-ui' interface Props { item?: GridWeapon @@ -57,40 +59,98 @@ async function remove() { if (!item?.id) return - const party = ctx.getParty() - const editKey = ctx.getEditKey() - const updated = await ctx.services.gridService.removeWeapon(party.id, item.id as any, editKey || undefined) - ctx.updateParty(updated) + try { + const party = ctx.getParty() + const editKey = ctx.getEditKey() + const updated = await ctx.services.gridService.removeWeapon(party.id, item.id as any, editKey || undefined) + if (updated) { + ctx.updateParty(updated) + } + } catch (err) { + console.error('Error removing weapon:', err) + } + } + + function viewDetails() { + // TODO: Implement view details modal + console.log('View details for:', item) + } + + function replace() { + if (ctx?.openPicker) { + ctx.openPicker({ type: 'weapon', position, item }) + } }
- {#key (item ? (item as any).id ?? position : `empty-${position}`)} -
ctx?.openPicker && ctx.openPicker({ type: 'weapon', position, item })} - > - {item - {#if !item && ctx?.canEdit()} - - - - {/if} - {#if ctx.canEdit() && item?.id} -
- -
- {/if} - {#if item?.mainhand || position === -1} - Main - {/if} -
- {/key} + {#if item} + + {#snippet children()} + {#key (item as any).id ?? position} +
+ {displayName(item?.weapon + {#if ctx?.canEdit() && item?.id} +
+ +
+ {/if} + {#if item?.mainhand || position === -1} + Main + {/if} +
+ {/key} + {/snippet} + + {#snippet menu()} + + View Details + + {#if ctx?.canEdit()} + + Replace + + + + Remove + + {/if} + {/snippet} +
+ {:else} + {#key `empty-${position}`} +
ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'weapon', position, item })} + > + + {#if ctx?.canEdit()} + + + + {/if} +
+ {/key} + {/if}
{item ? displayName(item?.weapon || (item as any)?.object) : ''}