add drag-drop support with API integration
- created drag-drop composable with touch/mouse support - added DraggableItem and DropZone components - integrated grids with drag-drop functionality - added API endpoints for position updates and swaps - handles cross-container dragging for all grid types
This commit is contained in:
parent
1d0495f1f2
commit
888e53fa62
5 changed files with 431 additions and 12 deletions
|
|
@ -536,6 +536,183 @@ export class APIClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update weapon position (drag-drop)
|
||||||
|
*/
|
||||||
|
async updateWeaponPosition(
|
||||||
|
partyId: string,
|
||||||
|
weaponId: string,
|
||||||
|
position: number,
|
||||||
|
container?: string
|
||||||
|
): Promise<any> {
|
||||||
|
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<any> {
|
||||||
|
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<any> {
|
||||||
|
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<any> {
|
||||||
|
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<any> {
|
||||||
|
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<any> {
|
||||||
|
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
|
* Get local ID for anonymous users
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,39 @@
|
||||||
import type { GridCharacter } from '$lib/types/api/party'
|
import type { GridCharacter } from '$lib/types/api/party'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import type { PartyContext } from '$lib/services/party.service'
|
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 {
|
interface Props {
|
||||||
characters?: GridCharacter[]
|
characters?: GridCharacter[]
|
||||||
mainWeaponElement?: number | null | undefined
|
mainWeaponElement?: number | null | undefined
|
||||||
partyElement?: 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'
|
import CharacterUnit from '$lib/components/units/CharacterUnit.svelte'
|
||||||
|
|
||||||
const ctx = getContext<PartyContext>('party')
|
const ctx = getContext<PartyContext>('party')
|
||||||
|
const dragContext = getContext<DragDropContext | undefined>('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
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
@ -23,14 +44,33 @@
|
||||||
class="characters"
|
class="characters"
|
||||||
aria-label="Character Grid"
|
aria-label="Character Grid"
|
||||||
>
|
>
|
||||||
{#each Array(5) as _, i}
|
{#each characterSlots() as character, i}
|
||||||
{@const character = characters.find((c) => c.position === i)}
|
|
||||||
<li
|
<li
|
||||||
aria-label={`Character slot ${i}`}
|
aria-label={`Character slot ${i}`}
|
||||||
class:main-character={i === 0}
|
class:main-character={i === 0}
|
||||||
class:Empty={!character}
|
class:Empty={!character}
|
||||||
>
|
>
|
||||||
<CharacterUnit item={character} position={i} {mainWeaponElement} {partyElement} />
|
{#if dragContext}
|
||||||
|
<DropZone
|
||||||
|
{container}
|
||||||
|
position={i}
|
||||||
|
type="character"
|
||||||
|
item={character}
|
||||||
|
canDrop={ctx?.canEdit() ?? false}
|
||||||
|
>
|
||||||
|
<DraggableItem
|
||||||
|
item={character}
|
||||||
|
{container}
|
||||||
|
position={i}
|
||||||
|
type="character"
|
||||||
|
canDrag={!!character && (ctx?.canEdit() ?? false)}
|
||||||
|
>
|
||||||
|
<CharacterUnit item={character} position={i} {mainWeaponElement} {partyElement} />
|
||||||
|
</DraggableItem>
|
||||||
|
</DropZone>
|
||||||
|
{:else}
|
||||||
|
<CharacterUnit item={character} position={i} {mainWeaponElement} {partyElement} />
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
import type { GridSummon } from '$lib/types/api/party'
|
import type { GridSummon } from '$lib/types/api/party'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import type { PartyContext } from '$lib/services/party.service'
|
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 {
|
interface Props {
|
||||||
summons?: GridSummon[]
|
summons?: GridSummon[]
|
||||||
|
|
@ -15,9 +18,21 @@
|
||||||
import ExtraSummons from '$lib/components/extra/ExtraSummonsGrid.svelte'
|
import ExtraSummons from '$lib/components/extra/ExtraSummonsGrid.svelte'
|
||||||
|
|
||||||
const ctx = getContext<PartyContext>('party')
|
const ctx = getContext<PartyContext>('party')
|
||||||
|
const dragContext = getContext<DragDropContext | undefined>('drag-drop')
|
||||||
|
|
||||||
let main = $derived(summons.find((s) => s.main || s.position === -1))
|
let main = $derived(summons.find((s) => s.main || s.position === -1))
|
||||||
let friend = $derived(summons.find((s) => s.friend || s.position === 6))
|
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
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
@ -30,13 +45,32 @@
|
||||||
<section>
|
<section>
|
||||||
<div class="label">Summons</div>
|
<div class="label">Summons</div>
|
||||||
<ul class="summons">
|
<ul class="summons">
|
||||||
{#each Array(4) as _, i}
|
{#each subSummonSlots() as summon, i}
|
||||||
{@const summon = summons.find((s) => s.position === i)}
|
|
||||||
<li
|
<li
|
||||||
aria-label={`Summon slot ${i}`}
|
aria-label={`Summon slot ${i}`}
|
||||||
class:Empty={!summon}
|
class:Empty={!summon}
|
||||||
>
|
>
|
||||||
<SummonUnit item={summon} position={i} />
|
{#if dragContext}
|
||||||
|
<DropZone
|
||||||
|
container="main-summons"
|
||||||
|
position={i}
|
||||||
|
type="summon"
|
||||||
|
item={summon}
|
||||||
|
canDrop={ctx?.canEdit() ?? false}
|
||||||
|
>
|
||||||
|
<DraggableItem
|
||||||
|
item={summon}
|
||||||
|
container="main-summons"
|
||||||
|
position={i}
|
||||||
|
type="summon"
|
||||||
|
canDrag={!!summon && (ctx?.canEdit() ?? false)}
|
||||||
|
>
|
||||||
|
<SummonUnit item={summon} position={i} />
|
||||||
|
</DraggableItem>
|
||||||
|
</DropZone>
|
||||||
|
{:else}
|
||||||
|
<SummonUnit item={summon} position={i} />
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
import type { GridWeapon } from '$lib/types/api/party'
|
import type { GridWeapon } from '$lib/types/api/party'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import type { PartyContext } from '$lib/services/party.service'
|
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 {
|
interface Props {
|
||||||
weapons?: GridWeapon[]
|
weapons?: GridWeapon[]
|
||||||
|
|
@ -24,8 +27,20 @@
|
||||||
import Guidebooks from '$lib/components/extra/GuidebooksGrid.svelte'
|
import Guidebooks from '$lib/components/extra/GuidebooksGrid.svelte'
|
||||||
|
|
||||||
const ctx = getContext<PartyContext>('party')
|
const ctx = getContext<PartyContext>('party')
|
||||||
|
const dragContext = getContext<DragDropContext | undefined>('drag-drop')
|
||||||
|
|
||||||
let mainhand = $derived(weapons.find((w) => (w as any).mainhand || w.position === -1))
|
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
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
@ -35,14 +50,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="weapons" aria-label="Weapon Grid">
|
<ul class="weapons" aria-label="Weapon Grid">
|
||||||
{#each Array(9) as _, i}
|
{#each subWeaponSlots() as weapon, i}
|
||||||
{@const weapon = weapons.find((w) => w.position === i)}
|
|
||||||
<li
|
<li
|
||||||
aria-label={weapon ? `Weapon ${i}` : `Empty slot ${i}`}
|
aria-label={weapon ? `Weapon ${i}` : `Empty slot ${i}`}
|
||||||
data-index={i}
|
data-index={i}
|
||||||
class={weapon ? '' : 'Empty'}
|
class={weapon ? '' : 'Empty'}
|
||||||
>
|
>
|
||||||
<WeaponUnit item={weapon} position={i} />
|
{#if dragContext}
|
||||||
|
<DropZone
|
||||||
|
container="main-weapons"
|
||||||
|
position={i}
|
||||||
|
type="weapon"
|
||||||
|
item={weapon}
|
||||||
|
canDrop={ctx?.canEdit() ?? false}
|
||||||
|
>
|
||||||
|
<DraggableItem
|
||||||
|
item={weapon}
|
||||||
|
container="main-weapons"
|
||||||
|
position={i}
|
||||||
|
type="weapon"
|
||||||
|
canDrag={!!weapon && (ctx?.canEdit() ?? false)}
|
||||||
|
>
|
||||||
|
<WeaponUnit item={weapon} position={i} />
|
||||||
|
</DraggableItem>
|
||||||
|
</DropZone>
|
||||||
|
{:else}
|
||||||
|
<WeaponUnit item={weapon} position={i} />
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, getContext, setContext } from 'svelte'
|
import { onMount, getContext, setContext } from 'svelte'
|
||||||
import type { Party } from '$lib/types/api/party'
|
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
||||||
import { PartyService } from '$lib/services/party.service'
|
import { PartyService } from '$lib/services/party.service'
|
||||||
import { GridService } from '$lib/services/grid.service'
|
import { GridService } from '$lib/services/grid.service'
|
||||||
import { ConflictService } from '$lib/services/conflict.service'
|
import { ConflictService } from '$lib/services/conflict.service'
|
||||||
import { apiClient } from '$lib/api/client'
|
import { apiClient } from '$lib/api/client'
|
||||||
|
import { createDragDropContext, type DragOperation } from '$lib/composables/drag-drop.svelte'
|
||||||
import WeaponGrid from '$lib/components/grids/WeaponGrid.svelte'
|
import WeaponGrid from '$lib/components/grids/WeaponGrid.svelte'
|
||||||
import SummonGrid from '$lib/components/grids/SummonGrid.svelte'
|
import SummonGrid from '$lib/components/grids/SummonGrid.svelte'
|
||||||
import CharacterGrid from '$lib/components/grids/CharacterGrid.svelte'
|
import CharacterGrid from '$lib/components/grids/CharacterGrid.svelte'
|
||||||
|
|
@ -51,6 +52,136 @@
|
||||||
const gridService = new GridService(fetch)
|
const gridService = new GridService(fetch)
|
||||||
const conflictService = new ConflictService(fetch)
|
const conflictService = new ConflictService(fetch)
|
||||||
|
|
||||||
|
// Create drag-drop context
|
||||||
|
const dragContext = createDragDropContext({
|
||||||
|
onLocalUpdate: async (operation) => {
|
||||||
|
console.log('📝 Drag operation:', operation)
|
||||||
|
await handleDragOperation(operation)
|
||||||
|
},
|
||||||
|
onValidate: (source, target) => {
|
||||||
|
// Type must match
|
||||||
|
if (source.type !== target.type) return false
|
||||||
|
|
||||||
|
// Characters: Sequential filling
|
||||||
|
if (source.type === 'character' && target.container === 'main-characters') {
|
||||||
|
// For now, allow any position (we'll handle sequential filling in the operation)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weapons: Mainhand not draggable
|
||||||
|
if (target.type === 'weapon' && target.position === -1) return false
|
||||||
|
|
||||||
|
// Summons: Main/Friend not draggable
|
||||||
|
if (target.type === 'summon' && (target.position === -1 || target.position === 6)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle drag operations
|
||||||
|
async function handleDragOperation(operation: DragOperation) {
|
||||||
|
if (!canEdit()) return
|
||||||
|
|
||||||
|
const { source, target } = operation
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading = true
|
||||||
|
let updated: Party | null = null
|
||||||
|
|
||||||
|
if (operation.type === 'swap') {
|
||||||
|
// Handle swapping items between positions
|
||||||
|
updated = await handleSwap(source, target)
|
||||||
|
} else if (operation.type === 'move') {
|
||||||
|
// Handle moving to empty position
|
||||||
|
updated = await handleMove(source, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update party with returned data from API
|
||||||
|
if (updated) {
|
||||||
|
party = updated
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
error = err.message || 'Failed to update party'
|
||||||
|
console.error('Drag operation failed:', err)
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
dragContext.clearQueue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSwap(source: any, target: any): Promise<Party> {
|
||||||
|
if (!party.id || party.id === 'new') {
|
||||||
|
throw new Error('Cannot swap items in unsaved party')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both source and target should have items for swap
|
||||||
|
if (!source.itemId || !target.itemId) {
|
||||||
|
throw new Error('Invalid swap operation - missing items')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call appropriate API based on type
|
||||||
|
if (source.type === 'weapon') {
|
||||||
|
const updated = await apiClient.swapWeapons(party.id, source.itemId, target.itemId)
|
||||||
|
return updated
|
||||||
|
} else if (source.type === 'character') {
|
||||||
|
const updated = await apiClient.swapCharacters(party.id, source.itemId, target.itemId)
|
||||||
|
return updated
|
||||||
|
} else if (source.type === 'summon') {
|
||||||
|
const updated = await apiClient.swapSummons(party.id, source.itemId, target.itemId)
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown item type: ${source.type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMove(source: any, target: any): Promise<Party> {
|
||||||
|
if (!party.id || party.id === 'new') {
|
||||||
|
throw new Error('Cannot move items in unsaved party')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source should have an item, target should be empty
|
||||||
|
if (!source.itemId || target.itemId) {
|
||||||
|
throw new Error('Invalid move operation')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine container based on target position
|
||||||
|
let container: string | undefined
|
||||||
|
|
||||||
|
if (source.type === 'character') {
|
||||||
|
// Characters: positions 0-4 are main, 5-6 are extra
|
||||||
|
container = target.position >= 5 ? 'extra' : 'main'
|
||||||
|
const updated = await apiClient.updateCharacterPosition(
|
||||||
|
party.id,
|
||||||
|
source.itemId,
|
||||||
|
target.position,
|
||||||
|
container
|
||||||
|
)
|
||||||
|
return updated
|
||||||
|
} else if (source.type === 'weapon') {
|
||||||
|
// Weapons: positions 0-8 are main, 9+ are extra
|
||||||
|
container = target.position >= 9 ? 'extra' : 'main'
|
||||||
|
const updated = await apiClient.updateWeaponPosition(
|
||||||
|
party.id,
|
||||||
|
source.itemId,
|
||||||
|
target.position,
|
||||||
|
container
|
||||||
|
)
|
||||||
|
return updated
|
||||||
|
} else if (source.type === 'summon') {
|
||||||
|
// Summons: positions 0-3 are sub, 4-5 are subaura
|
||||||
|
container = target.position >= 4 ? 'subaura' : 'main'
|
||||||
|
const updated = await apiClient.updateSummonPosition(
|
||||||
|
party.id,
|
||||||
|
source.itemId,
|
||||||
|
target.position,
|
||||||
|
container
|
||||||
|
)
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown item type: ${source.type}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Localized name helper: accepts either an object with { name: { en, ja } }
|
// Localized name helper: accepts either an object with { name: { en, ja } }
|
||||||
// or a direct { en, ja } map, or a plain string.
|
// or a direct { en, ja } map, or a plain string.
|
||||||
function displayName(input: any): string {
|
function displayName(input: any): string {
|
||||||
|
|
@ -329,7 +460,7 @@
|
||||||
// Provide services to child components via context
|
// Provide services to child components via context
|
||||||
setContext('party', {
|
setContext('party', {
|
||||||
getParty: () => party,
|
getParty: () => party,
|
||||||
updateParty: (p: PartyView) => party = p,
|
updateParty: (p: Party) => party = p,
|
||||||
canEdit: () => canEdit(),
|
canEdit: () => canEdit(),
|
||||||
getEditKey: () => editKey,
|
getEditKey: () => editKey,
|
||||||
services: {
|
services: {
|
||||||
|
|
@ -346,6 +477,9 @@
|
||||||
pickerOpen = true
|
pickerOpen = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Provide drag-drop context to child components
|
||||||
|
setContext('drag-drop', dragContext)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page-wrap" class:with-panel={pickerOpen}>
|
<div class="page-wrap" class:with-panel={pickerOpen}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue