refactor: restructure party component layout
This commit is contained in:
parent
e4c59e14f6
commit
0bab6e0d7e
1 changed files with 826 additions and 713 deletions
|
|
@ -14,6 +14,7 @@
|
|||
import type { SearchResult } from '$lib/api/resources/search'
|
||||
import { GridType } from '$lib/types/enums'
|
||||
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||
import Button from '$lib/components/ui/button/Button.svelte'
|
||||
|
||||
interface Props {
|
||||
party?: Party
|
||||
|
|
@ -36,9 +37,7 @@
|
|||
|
||||
// Initialize party state with proper validation
|
||||
let party = $state<Party>(
|
||||
initial?.id && initial?.id !== 'new' && Array.isArray(initial?.weapons)
|
||||
? initial
|
||||
: defaultParty
|
||||
initial?.id && initial?.id !== 'new' && Array.isArray(initial?.weapons) ? initial : defaultParty
|
||||
)
|
||||
let activeTab = $state<GridType>(GridType.Weapon)
|
||||
let loading = $state(false)
|
||||
|
|
@ -74,7 +73,8 @@
|
|||
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
|
||||
if (target.type === 'summon' && (target.position === -1 || target.position === 6))
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -205,17 +205,14 @@
|
|||
if (canEditServer) return true
|
||||
|
||||
// Re-compute on client with localStorage values
|
||||
const result = partyService.computeEditability(
|
||||
party,
|
||||
authUserId,
|
||||
localId,
|
||||
editKey
|
||||
)
|
||||
const result = partyService.computeEditability(party, authUserId, localId, editKey)
|
||||
return result.canEdit
|
||||
})
|
||||
|
||||
// Derived elements for character image logic
|
||||
const mainWeapon = $derived(() => (party?.weapons ?? []).find(w => w?.mainhand || w?.position === -1))
|
||||
const mainWeapon = $derived(() =>
|
||||
(party?.weapons ?? []).find((w) => w?.mainhand || w?.position === -1)
|
||||
)
|
||||
const mainWeaponElement = $derived(() => mainWeapon?.element ?? mainWeapon?.weapon?.element)
|
||||
const partyElement = $derived(() => party?.element)
|
||||
|
||||
|
|
@ -296,11 +293,7 @@
|
|||
error = null
|
||||
|
||||
try {
|
||||
const result = await partyService.remix(
|
||||
party.shortcode,
|
||||
localId,
|
||||
editKey || undefined
|
||||
)
|
||||
const result = await partyService.remix(party.shortcode, localId, editKey || undefined)
|
||||
|
||||
// Store new edit key if returned
|
||||
if (result.editKey) {
|
||||
|
|
@ -351,12 +344,12 @@
|
|||
|
||||
if (activeTab === GridType.Weapon) {
|
||||
// Check mainhand first (position -1)
|
||||
if (!party.weapons.find(w => w.position === -1 || w.mainhand)) {
|
||||
if (!party.weapons.find((w) => w.position === -1 || w.mainhand)) {
|
||||
nextEmptySlot = -1
|
||||
} else {
|
||||
// Check grid slots 0-8
|
||||
for (let i = 0; i < 9; i++) {
|
||||
if (!party.weapons.find(w => w.position === i)) {
|
||||
if (!party.weapons.find((w) => w.position === i)) {
|
||||
nextEmptySlot = i
|
||||
break
|
||||
}
|
||||
|
|
@ -364,25 +357,25 @@
|
|||
}
|
||||
} else if (activeTab === GridType.Summon) {
|
||||
// Check main summon first (position -1)
|
||||
if (!party.summons.find(s => s.position === -1 || s.main)) {
|
||||
if (!party.summons.find((s) => s.position === -1 || s.main)) {
|
||||
nextEmptySlot = -1
|
||||
} else {
|
||||
// Check grid slots 0-5
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (!party.summons.find(s => s.position === i)) {
|
||||
if (!party.summons.find((s) => s.position === i)) {
|
||||
nextEmptySlot = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// Check friend summon (position 6)
|
||||
if (nextEmptySlot === -999 && !party.summons.find(s => s.position === 6 || s.friend)) {
|
||||
if (nextEmptySlot === -999 && !party.summons.find((s) => s.position === 6 || s.friend)) {
|
||||
nextEmptySlot = 6
|
||||
}
|
||||
}
|
||||
} else if (activeTab === GridType.Character) {
|
||||
// Check character slots 0-4
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (!party.characters.find(c => c.position === i)) {
|
||||
if (!party.characters.find((c) => c.position === i)) {
|
||||
nextEmptySlot = i
|
||||
break
|
||||
}
|
||||
|
|
@ -449,13 +442,121 @@
|
|||
console.error('Failed to remove character:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateWeapon(partyId: string, gridWeaponId: string, updates: any, _editKey?: string) {
|
||||
try {
|
||||
// Use the grid service to update weapon
|
||||
const updated = await gridService.updateWeapon(partyId, gridWeaponId, updates, editKey || undefined)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to update weapon:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateSummon(partyId: string, gridSummonId: string, updates: any, _editKey?: string) {
|
||||
try {
|
||||
// Use the grid service to update summon
|
||||
const updated = await gridService.updateSummon(partyId, gridSummonId, updates, editKey || undefined)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to update summon:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateCharacter(partyId: string, gridCharacterId: string, updates: any, _editKey?: string) {
|
||||
try {
|
||||
// Use the grid service to update character
|
||||
const updated = await gridService.updateCharacter(partyId, gridCharacterId, updates, editKey || undefined)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to update character:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateCharacterUncap(gridCharacterId: string, uncapLevel?: number, transcendenceStep?: number, _editKey?: string) {
|
||||
try {
|
||||
const response = await gridService.updateCharacterUncap(gridCharacterId, uncapLevel, transcendenceStep, editKey || undefined)
|
||||
// The API returns {grid_character: {...}} with the updated item only
|
||||
// We need to update just that character in the current party state
|
||||
if (response.grid_character) {
|
||||
const updatedParty = { ...party }
|
||||
if (updatedParty.characters) {
|
||||
const charIndex = updatedParty.characters.findIndex((c: any) => c.id === gridCharacterId)
|
||||
if (charIndex !== -1) {
|
||||
// Preserve the character object reference but update uncap fields
|
||||
updatedParty.characters[charIndex] = {
|
||||
...updatedParty.characters[charIndex],
|
||||
uncapLevel: response.grid_character.uncap_level,
|
||||
transcendenceStep: response.grid_character.transcendence_step
|
||||
}
|
||||
return updatedParty
|
||||
}
|
||||
}
|
||||
}
|
||||
return party // Return unchanged party if update failed
|
||||
} catch (err) {
|
||||
console.error('Failed to update character uncap:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateWeaponUncap(gridWeaponId: string, uncapLevel?: number, transcendenceStep?: number, _editKey?: string) {
|
||||
try {
|
||||
const response = await gridService.updateWeaponUncap(gridWeaponId, uncapLevel, transcendenceStep, editKey || undefined)
|
||||
// The API returns {grid_weapon: {...}} with the updated item only
|
||||
// We need to update just that weapon in the current party state
|
||||
if (response.grid_weapon) {
|
||||
const updatedParty = { ...party }
|
||||
if (updatedParty.weapons) {
|
||||
const weaponIndex = updatedParty.weapons.findIndex((w: any) => w.id === gridWeaponId)
|
||||
if (weaponIndex !== -1) {
|
||||
// Preserve the weapon object reference but update uncap fields
|
||||
updatedParty.weapons[weaponIndex] = {
|
||||
...updatedParty.weapons[weaponIndex],
|
||||
uncapLevel: response.grid_weapon.uncap_level,
|
||||
transcendenceStep: response.grid_weapon.transcendence_step
|
||||
}
|
||||
return updatedParty
|
||||
}
|
||||
}
|
||||
}
|
||||
return party // Return unchanged party if update failed
|
||||
} catch (err) {
|
||||
console.error('Failed to update weapon uncap:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async updateSummonUncap(gridSummonId: string, uncapLevel?: number, transcendenceStep?: number, _editKey?: string) {
|
||||
try {
|
||||
const response = await gridService.updateSummonUncap(gridSummonId, uncapLevel, transcendenceStep, editKey || undefined)
|
||||
// The API returns {grid_summon: {...}} with the updated item only
|
||||
// We need to update just that summon in the current party state
|
||||
if (response.grid_summon) {
|
||||
const updatedParty = { ...party }
|
||||
if (updatedParty.summons) {
|
||||
const summonIndex = updatedParty.summons.findIndex((s: any) => s.id === gridSummonId)
|
||||
if (summonIndex !== -1) {
|
||||
// Preserve the summon object reference but update uncap fields
|
||||
updatedParty.summons[summonIndex] = {
|
||||
...updatedParty.summons[summonIndex],
|
||||
uncapLevel: response.grid_summon.uncap_level,
|
||||
transcendenceStep: response.grid_summon.transcendence_step
|
||||
}
|
||||
return updatedParty
|
||||
}
|
||||
}
|
||||
}
|
||||
return party // Return unchanged party if update failed
|
||||
} catch (err) {
|
||||
console.error('Failed to update summon uncap:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide services to child components via context
|
||||
setContext('party', {
|
||||
getParty: () => party,
|
||||
updateParty: (p: Party) => party = p,
|
||||
updateParty: (p: Party) => (party = p),
|
||||
canEdit: () => canEdit(),
|
||||
getEditKey: () => editKey,
|
||||
services: {
|
||||
|
|
@ -463,11 +564,19 @@
|
|||
gridService: clientGridService, // Use client-side wrapper
|
||||
conflictService
|
||||
},
|
||||
openPicker: (opts: { type: 'weapon' | 'summon' | 'character'; position: number; item?: any }) => {
|
||||
openPicker: (opts: {
|
||||
type: 'weapon' | 'summon' | 'character'
|
||||
position: number
|
||||
item?: any
|
||||
}) => {
|
||||
if (!canEdit()) return
|
||||
selectedSlot = opts.position
|
||||
activeTab = opts.type === 'weapon' ? GridType.Weapon :
|
||||
opts.type === 'summon' ? GridType.Summon : GridType.Character
|
||||
activeTab =
|
||||
opts.type === 'weapon'
|
||||
? GridType.Weapon
|
||||
: opts.type === 'summon'
|
||||
? GridType.Summon
|
||||
: GridType.Character
|
||||
pickerTitle = `Search ${opts.type}s`
|
||||
pickerOpen = true
|
||||
}
|
||||
|
|
@ -490,36 +599,35 @@
|
|||
|
||||
<div class="party-actions">
|
||||
{#if canEdit()}
|
||||
<button
|
||||
class="edit-btn"
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={openEditDialog}
|
||||
disabled={loading}
|
||||
aria-label="Edit party details"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if authUserId}
|
||||
<button
|
||||
class="favorite-btn"
|
||||
class:favorited={party.favorited}
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={toggleFavorite}
|
||||
disabled={loading}
|
||||
aria-label={party.favorited ? 'Remove from favorites' : 'Add to favorites'}
|
||||
>
|
||||
{party.favorited ? '★' : '☆'}
|
||||
</button>
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="remix-btn"
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={remixParty}
|
||||
disabled={loading}
|
||||
aria-label="Remix this party"
|
||||
>
|
||||
Remix
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -528,15 +636,15 @@
|
|||
✏️ You can edit this party - Click on any slot to add or replace items
|
||||
</div>
|
||||
{:else}
|
||||
<div class="edit-status readonly">
|
||||
🔒 Read-only
|
||||
</div>
|
||||
<div class="edit-status readonly">🔒 Read-only</div>
|
||||
{/if}
|
||||
|
||||
{#if party.raid}
|
||||
<div class="raid-info">
|
||||
<span class="raid-name">
|
||||
{typeof party.raid.name === 'string' ? party.raid.name : party.raid.name?.en || party.raid.name?.ja || 'Unknown Raid'}
|
||||
{typeof party.raid.name === 'string'
|
||||
? party.raid.name
|
||||
: party.raid.name?.en || party.raid.name?.ja || 'Unknown Raid'}
|
||||
</span>
|
||||
{#if party.raid.group}
|
||||
<span class="raid-difficulty">Difficulty: {party.raid.group.difficulty}</span>
|
||||
|
|
@ -547,7 +655,7 @@
|
|||
<PartySegmentedControl
|
||||
selectedTab={activeTab}
|
||||
onTabChange={handleTabChange}
|
||||
party={party}
|
||||
{party}
|
||||
class="party-tabs"
|
||||
/>
|
||||
|
||||
|
|
@ -568,16 +676,17 @@
|
|||
{:else if activeTab === GridType.Summon}
|
||||
<SummonGrid summons={party.summons} />
|
||||
{:else}
|
||||
<CharacterGrid characters={party.characters} mainWeaponElement={mainWeaponElement} partyElement={partyElement} />
|
||||
<CharacterGrid characters={party.characters} {mainWeaponElement} {partyElement} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
<SearchSidebar
|
||||
open={pickerOpen}
|
||||
type={activeTab === GridType.Weapon ? 'weapon' :
|
||||
activeTab === GridType.Summon ? 'summon' : 'character'}
|
||||
type={activeTab === GridType.Weapon
|
||||
? 'weapon'
|
||||
: activeTab === GridType.Summon
|
||||
? 'summon'
|
||||
: 'character'}
|
||||
onClose={() => (pickerOpen = false)}
|
||||
onAddItems={handleAddItems}
|
||||
canAddMore={true}
|
||||
|
|
@ -601,27 +710,31 @@
|
|||
{/snippet}
|
||||
|
||||
{#snippet footer()}
|
||||
<button
|
||||
class="btn-secondary"
|
||||
onclick={() => (editDialogOpen = false)}
|
||||
disabled={loading}
|
||||
>
|
||||
<button class="btn-secondary" onclick={() => (editDialogOpen = false)} disabled={loading}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn-primary"
|
||||
onclick={savePartyTitle}
|
||||
disabled={loading || !editingTitle.trim()}
|
||||
>
|
||||
<button class="btn-primary" onclick={savePartyTitle} disabled={loading || !editingTitle.trim()}>
|
||||
{loading ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
|
||||
<style>
|
||||
.page-wrap { position: relative; --panel-w: 380px; overflow-x: auto; }
|
||||
.track { display: flex; gap: 0; align-items: flex-start; }
|
||||
.party-container { width: 1200px; margin: 0 auto; padding: 1rem; }
|
||||
.page-wrap {
|
||||
position: relative;
|
||||
--panel-w: 380px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.track {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.party-container {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.party-header {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in a new issue