diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index 5fffeb34..858c6c0e 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -536,6 +536,183 @@ export class APIClient { } } + /** + * Update weapon position (drag-drop) + */ + async updateWeaponPosition( + partyId: string, + weaponId: string, + position: number, + container?: string + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_weapons/${weaponId}/position`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + position, + ...(container ? { container } : {}) + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update weapon position: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + + /** + * Swap two weapons (drag-drop) + */ + async swapWeapons(partyId: string, sourceId: string, targetId: string): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_weapons/swap`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + source_id: sourceId, + target_id: targetId + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to swap weapons: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + + /** + * Update character position (drag-drop) + */ + async updateCharacterPosition( + partyId: string, + characterId: string, + position: number, + container?: string + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_characters/${characterId}/position`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + position, + ...(container ? { container } : {}) + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update character position: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + + /** + * Swap two characters (drag-drop) + */ + async swapCharacters(partyId: string, sourceId: string, targetId: string): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_characters/swap`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + source_id: sourceId, + target_id: targetId + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to swap characters: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + + /** + * Update summon position (drag-drop) + */ + async updateSummonPosition( + partyId: string, + summonId: string, + position: number, + container?: string + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_summons/${summonId}/position`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + position, + ...(container ? { container } : {}) + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update summon position: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + + /** + * Swap two summons (drag-drop) + */ + async swapSummons(partyId: string, sourceId: string, targetId: string): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/grid_summons/swap`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify({ + source_id: sourceId, + target_id: targetId + }) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to swap summons: ${response.statusText}`) + } + + const data = await response.json() + return transformResponse(data.party || data) + } + /** * Get local ID for anonymous users */ diff --git a/src/lib/components/grids/CharacterGrid.svelte b/src/lib/components/grids/CharacterGrid.svelte index 87c0e5f0..4ccb1f10 100644 --- a/src/lib/components/grids/CharacterGrid.svelte +++ b/src/lib/components/grids/CharacterGrid.svelte @@ -4,18 +4,39 @@ import type { GridCharacter } from '$lib/types/api/party' import { getContext } from 'svelte' import type { PartyContext } from '$lib/services/party.service' + import type { DragDropContext } from '$lib/composables/drag-drop.svelte' + import DraggableItem from '$lib/components/dnd/DraggableItem.svelte' + import DropZone from '$lib/components/dnd/DropZone.svelte' interface Props { characters?: GridCharacter[] mainWeaponElement?: number | null | undefined partyElement?: number | null | undefined + container?: string } - let { characters = [], mainWeaponElement = undefined, partyElement = undefined }: Props = $props() + let { + characters = [], + mainWeaponElement = undefined, + partyElement = undefined, + container = 'main-characters' + }: Props = $props() import CharacterUnit from '$lib/components/units/CharacterUnit.svelte' const ctx = getContext('party') + const dragContext = getContext('drag-drop') + + // Create array with proper empty slots + let characterSlots = $derived(() => { + const slots: (GridCharacter | undefined)[] = Array(5).fill(undefined) + characters.forEach(char => { + if (char.position >= 0 && char.position < 5) { + slots[char.position] = char + } + }) + return slots + })
@@ -23,14 +44,33 @@ class="characters" aria-label="Character Grid" > - {#each Array(5) as _, i} - {@const character = characters.find((c) => c.position === i)} + {#each characterSlots() as character, i}
  • - + {#if dragContext} + + + + + + {:else} + + {/if}
  • {/each} diff --git a/src/lib/components/grids/SummonGrid.svelte b/src/lib/components/grids/SummonGrid.svelte index e09d81d5..3cfbb771 100644 --- a/src/lib/components/grids/SummonGrid.svelte +++ b/src/lib/components/grids/SummonGrid.svelte @@ -4,6 +4,9 @@ import type { GridSummon } from '$lib/types/api/party' import { getContext } from 'svelte' import type { PartyContext } from '$lib/services/party.service' + import type { DragDropContext } from '$lib/composables/drag-drop.svelte' + import DraggableItem from '$lib/components/dnd/DraggableItem.svelte' + import DropZone from '$lib/components/dnd/DropZone.svelte' interface Props { summons?: GridSummon[] @@ -15,9 +18,21 @@ import ExtraSummons from '$lib/components/extra/ExtraSummonsGrid.svelte' const ctx = getContext('party') + const dragContext = getContext('drag-drop') let main = $derived(summons.find((s) => s.main || s.position === -1)) let friend = $derived(summons.find((s) => s.friend || s.position === 6)) + + // Create array for sub-summons (positions 0-3) + let subSummonSlots = $derived(() => { + const slots: (GridSummon | undefined)[] = Array(4).fill(undefined) + summons.forEach(summon => { + if (summon.position >= 0 && summon.position < 4) { + slots[summon.position] = summon + } + }) + return slots + })
    @@ -30,13 +45,32 @@
    Summons
      - {#each Array(4) as _, i} - {@const summon = summons.find((s) => s.position === i)} + {#each subSummonSlots() as summon, i}
    • - + {#if dragContext} + + + + + + {:else} + + {/if}
    • {/each}
    diff --git a/src/lib/components/grids/WeaponGrid.svelte b/src/lib/components/grids/WeaponGrid.svelte index 2841bd81..041de857 100644 --- a/src/lib/components/grids/WeaponGrid.svelte +++ b/src/lib/components/grids/WeaponGrid.svelte @@ -4,6 +4,9 @@ import type { GridWeapon } from '$lib/types/api/party' import { getContext } from 'svelte' import type { PartyContext } from '$lib/services/party.service' + import type { DragDropContext } from '$lib/composables/drag-drop.svelte' + import DraggableItem from '$lib/components/dnd/DraggableItem.svelte' + import DropZone from '$lib/components/dnd/DropZone.svelte' interface Props { weapons?: GridWeapon[] @@ -24,8 +27,20 @@ import Guidebooks from '$lib/components/extra/GuidebooksGrid.svelte' const ctx = getContext('party') + const dragContext = getContext('drag-drop') let mainhand = $derived(weapons.find((w) => (w as any).mainhand || w.position === -1)) + + // Create array for sub-weapons (positions 0-8) + let subWeaponSlots = $derived(() => { + const slots: (GridWeapon | undefined)[] = Array(9).fill(undefined) + weapons.forEach(weapon => { + if (weapon.position >= 0 && weapon.position < 9) { + slots[weapon.position] = weapon + } + }) + return slots + })
    @@ -35,14 +50,33 @@
      - {#each Array(9) as _, i} - {@const weapon = weapons.find((w) => w.position === i)} + {#each subWeaponSlots() as weapon, i}
    • - + {#if dragContext} + + + + + + {:else} + + {/if}
    • {/each}
    diff --git a/src/lib/components/party/Party.svelte b/src/lib/components/party/Party.svelte index d33a0012..9205a966 100644 --- a/src/lib/components/party/Party.svelte +++ b/src/lib/components/party/Party.svelte @@ -1,10 +1,11 @@