Fix various spacing and sizing issues on party details
This commit is contained in:
parent
d8eb6b965a
commit
6ace86a00e
8 changed files with 949 additions and 831 deletions
|
|
@ -747,14 +747,14 @@
|
||||||
alt={`Avatar of ${party.user.username}`}
|
alt={`Avatar of ${party.user.username}`}
|
||||||
src={avatarSrc}
|
src={avatarSrc}
|
||||||
srcset={avatarSrcSet}
|
srcset={avatarSrcSet}
|
||||||
width="40"
|
width="32"
|
||||||
height="40"
|
height="32"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="avatar-placeholder" aria-hidden="true"></div>
|
<div class="avatar-placeholder" aria-hidden="true"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<span class="username">@{party.user.username}</span>
|
<span class="username">{party.user.username}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -940,17 +940,23 @@
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: $unit-half;
|
padding: $unit-half;
|
||||||
|
gap: $unit-2x;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.party-header {
|
.party-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
margin-bottom: $unit-half;
|
vertical-align: middle;
|
||||||
padding: $unit-4x 0;
|
align-items: center;
|
||||||
|
padding: $unit-2x 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.party-info {
|
.party-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0 0 $unit-fourth 0;
|
margin: 0 0 $unit-fourth 0;
|
||||||
font-size: $font-xlarge;
|
font-size: $font-xlarge;
|
||||||
|
|
@ -981,8 +987,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
width: $unit-5x;
|
width: $unit-4x;
|
||||||
height: $unit-5x;
|
height: $unit-4x;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
|
|
@ -1031,7 +1037,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
font-size: $font-medium;
|
font-size: $font-regular;
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1044,7 +1050,6 @@
|
||||||
.cards {
|
.cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $unit-2x;
|
gap: $unit-2x;
|
||||||
margin-bottom: $unit-2x;
|
|
||||||
|
|
||||||
// Individual card styles
|
// Individual card styles
|
||||||
.description-card,
|
.description-card,
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,7 @@
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let { selectedTab = GridType.Character, onTabChange, party, class: className }: Props = $props()
|
||||||
selectedTab = GridType.Character,
|
|
||||||
onTabChange,
|
|
||||||
party,
|
|
||||||
class: className
|
|
||||||
}: Props = $props()
|
|
||||||
|
|
||||||
|
|
||||||
// Handle value changes
|
// Handle value changes
|
||||||
let value = $state(selectedTab)
|
let value = $state(selectedTab)
|
||||||
|
|
@ -44,12 +38,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class={className}>
|
<nav class={className}>
|
||||||
<SegmentedControl
|
<SegmentedControl bind:value onValueChange={handleValueChange} gap={true} grow={true}>
|
||||||
bind:value
|
|
||||||
onValueChange={handleValueChange}
|
|
||||||
gap={true}
|
|
||||||
grow={true}
|
|
||||||
>
|
|
||||||
<RepSegment
|
<RepSegment
|
||||||
value={GridType.Character}
|
value={GridType.Character}
|
||||||
label={m.party_segmented_control_characters()}
|
label={m.party_segmented_control_characters()}
|
||||||
|
|
@ -82,8 +71,9 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -95,7 +95,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-section {
|
.actions-section {
|
||||||
margin-top: $unit-2x;
|
|
||||||
padding: $unit-2x;
|
padding: $unit-2x;
|
||||||
padding-bottom: $unit-2x;
|
padding-bottom: $unit-2x;
|
||||||
border-top: 1px solid var(--button-bg);
|
border-top: 1px solid var(--button-bg);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $item-corner;
|
border-radius: $card-corner;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: $grey-100;
|
background: $grey-100;
|
||||||
border-radius: $item-corner;
|
border-radius: $card-corner;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
.segmentedControl {
|
.segmentedControl {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 3px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
gap: spacing.$unit-half;
|
gap: spacing.$unit-half;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
@ -45,7 +44,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.gap {
|
&.gap {
|
||||||
gap: spacing.$unit;
|
gap: spacing.$unit-2x;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.raid {
|
&.raid {
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,29 @@
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
{#if data.user?.avatar?.picture}
|
{#if data.user?.avatar?.picture}
|
||||||
<img class="avatar" alt={`Avatar of ${data.user.username}`} src={avatarSrc} srcset={avatarSrcSet} width="64" height="64" />
|
<img
|
||||||
|
class="avatar"
|
||||||
|
alt={`Avatar of ${data.user.username}`}
|
||||||
|
src={avatarSrc}
|
||||||
|
srcset={avatarSrcSet}
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="avatar" aria-hidden="true"></div>
|
<div class="avatar" aria-hidden="true"></div>
|
||||||
{/if}
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<h1>@{data.user.username}</h1>
|
<h1>{data.user.username}</h1>
|
||||||
<nav class="tabs" aria-label="Profile sections">
|
<nav class="tabs" aria-label="Profile sections">
|
||||||
<a class:active={tab==='teams'} href="?tab=teams" data-sveltekit-preload-data="hover">Teams</a>
|
<a class:active={tab === 'teams'} href="?tab=teams" data-sveltekit-preload-data="hover"
|
||||||
|
>Teams</a
|
||||||
|
>
|
||||||
{#if isOwner}
|
{#if isOwner}
|
||||||
<a class:active={tab==='favorites'} href="?tab=favorites" data-sveltekit-preload-data="hover">Favorites</a>
|
<a
|
||||||
|
class:active={tab === 'favorites'}
|
||||||
|
href="?tab=favorites"
|
||||||
|
data-sveltekit-preload-data="hover">Favorites</a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -53,13 +66,48 @@
|
||||||
@use '$src/themes/spacing' as *;
|
@use '$src/themes/spacing' as *;
|
||||||
@use '$src/themes/colors' as *;
|
@use '$src/themes/colors' as *;
|
||||||
|
|
||||||
.profile { padding: $unit-2x 0; }
|
.profile {
|
||||||
.header { display: flex; align-items: center; gap: $unit-2x; margin-bottom: $unit-2x; }
|
padding: $unit-2x 0;
|
||||||
.avatar { width: 64px; height: 64px; border-radius: 50%; background: $grey-80; border: 1px solid $grey-75; object-fit: cover; }
|
}
|
||||||
.sub { color: $grey-55; margin: 0; }
|
.header {
|
||||||
.tabs { display: flex; gap: $unit-2x; margin-top: $unit-half; }
|
display: flex;
|
||||||
.tabs a { text-decoration: none; color: inherit; padding-bottom: 2px; border-bottom: 2px solid transparent; }
|
align-items: center;
|
||||||
.tabs a.active { border-color: #3366ff; color: #3366ff; }
|
gap: $unit-2x;
|
||||||
.pagination { display: flex; gap: $unit-2x; padding: $unit-2x 0; }
|
margin-bottom: $unit-2x;
|
||||||
.pagination a { text-decoration: none; }
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $grey-80;
|
||||||
|
border: 1px solid $grey-75;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.sub {
|
||||||
|
color: $grey-55;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-2x;
|
||||||
|
margin-top: $unit-half;
|
||||||
|
}
|
||||||
|
.tabs a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.tabs a.active {
|
||||||
|
border-color: #3366ff;
|
||||||
|
color: #3366ff;
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-2x;
|
||||||
|
padding: $unit-2x 0;
|
||||||
|
}
|
||||||
|
.pagination a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
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'
|
||||||
import { openSearchSidebar, closeSearchSidebar } from '$lib/features/search/openSearchSidebar.svelte'
|
import {
|
||||||
|
openSearchSidebar,
|
||||||
|
closeSearchSidebar
|
||||||
|
} from '$lib/features/search/openSearchSidebar.svelte'
|
||||||
import PartySegmentedControl from '$lib/components/party/PartySegmentedControl.svelte'
|
import PartySegmentedControl from '$lib/components/party/PartySegmentedControl.svelte'
|
||||||
import { GridType } from '$lib/types/enums'
|
import { GridType } from '$lib/types/enums'
|
||||||
import { setContext } from 'svelte'
|
import { setContext } from 'svelte'
|
||||||
|
|
@ -41,9 +44,12 @@
|
||||||
activeTab = gridType
|
activeTab = gridType
|
||||||
// Open sidebar when switching tabs
|
// Open sidebar when switching tabs
|
||||||
openSearchSidebar({
|
openSearchSidebar({
|
||||||
type: gridType === GridType.Weapon ? 'weapon' :
|
type:
|
||||||
gridType === GridType.Summon ? 'summon' :
|
gridType === GridType.Weapon
|
||||||
'character',
|
? 'weapon'
|
||||||
|
: gridType === GridType.Summon
|
||||||
|
? 'summon'
|
||||||
|
: 'character',
|
||||||
onAddItems: handleAddItems,
|
onAddItems: handleAddItems,
|
||||||
canAddMore: !isGridFull(gridType)
|
canAddMore: !isGridFull(gridType)
|
||||||
})
|
})
|
||||||
|
|
@ -74,16 +80,17 @@
|
||||||
let errorMessage = $state('')
|
let errorMessage = $state('')
|
||||||
let errorDetails = $state<string[]>([])
|
let errorDetails = $state<string[]>([])
|
||||||
|
|
||||||
|
|
||||||
// Calculate if grids are full
|
// Calculate if grids are full
|
||||||
let isWeaponGridFull = $derived(weapons.length >= 10) // 1 mainhand + 9 grid slots
|
let isWeaponGridFull = $derived(weapons.length >= 10) // 1 mainhand + 9 grid slots
|
||||||
let isSummonGridFull = $derived(summons.length >= 6) // 6 summon slots (main + 4 grid + friend)
|
let isSummonGridFull = $derived(summons.length >= 6) // 6 summon slots (main + 4 grid + friend)
|
||||||
let isCharacterGridFull = $derived(characters.length >= 5) // 5 character slots
|
let isCharacterGridFull = $derived(characters.length >= 5) // 5 character slots
|
||||||
|
|
||||||
let canAddMore = $derived(
|
let canAddMore = $derived(
|
||||||
activeTab === GridType.Weapon ? !isWeaponGridFull :
|
activeTab === GridType.Weapon
|
||||||
activeTab === GridType.Summon ? !isSummonGridFull :
|
? !isWeaponGridFull
|
||||||
!isCharacterGridFull
|
: activeTab === GridType.Summon
|
||||||
|
? !isSummonGridFull
|
||||||
|
: !isCharacterGridFull
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle adding items from search
|
// Handle adding items from search
|
||||||
|
|
@ -131,22 +138,26 @@
|
||||||
let position = selectedSlot !== null ? selectedSlot : -1 // Use selectedSlot if available
|
let position = selectedSlot !== null ? selectedSlot : -1 // Use selectedSlot if available
|
||||||
let itemAdded = false
|
let itemAdded = false
|
||||||
try {
|
try {
|
||||||
console.log('Adding item to party:', { partyId, itemId: firstItem.id, type: activeTab, position })
|
console.log('Adding item to party:', {
|
||||||
|
partyId,
|
||||||
|
itemId: firstItem.id,
|
||||||
|
type: activeTab,
|
||||||
|
position
|
||||||
|
})
|
||||||
|
|
||||||
if (activeTab === GridType.Weapon) {
|
if (activeTab === GridType.Weapon) {
|
||||||
// Use selectedSlot if available, otherwise default to mainhand
|
// Use selectedSlot if available, otherwise default to mainhand
|
||||||
if (selectedSlot === null) position = -1
|
if (selectedSlot === null) position = -1
|
||||||
const addResult = await apiClient.addWeapon(
|
const addResult = await apiClient.addWeapon(partyId, firstItem.granblue_id, position, {
|
||||||
partyId,
|
mainhand: position === -1
|
||||||
firstItem.granblue_id,
|
})
|
||||||
position,
|
|
||||||
{ mainhand: position === -1 }
|
|
||||||
)
|
|
||||||
console.log('Weapon added:', addResult)
|
console.log('Weapon added:', addResult)
|
||||||
itemAdded = true
|
itemAdded = true
|
||||||
|
|
||||||
// Update local state with the added weapon
|
// Update local state with the added weapon
|
||||||
weapons = [...weapons, {
|
weapons = [
|
||||||
|
...weapons,
|
||||||
|
{
|
||||||
id: addResult.grid_weapon?.id || `temp-${Date.now()}`,
|
id: addResult.grid_weapon?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -155,21 +166,22 @@
|
||||||
element: firstItem.element
|
element: firstItem.element
|
||||||
},
|
},
|
||||||
mainhand: position === -1
|
mainhand: position === -1
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
} else if (activeTab === GridType.Summon) {
|
} else if (activeTab === GridType.Summon) {
|
||||||
// Use selectedSlot if available, otherwise default to main summon
|
// Use selectedSlot if available, otherwise default to main summon
|
||||||
if (selectedSlot === null) position = -1
|
if (selectedSlot === null) position = -1
|
||||||
const addResult = await apiClient.addSummon(
|
const addResult = await apiClient.addSummon(partyId, firstItem.granblue_id, position, {
|
||||||
partyId,
|
main: position === -1,
|
||||||
firstItem.granblue_id,
|
friend: position === 6
|
||||||
position,
|
})
|
||||||
{ main: position === -1, friend: position === 6 }
|
|
||||||
)
|
|
||||||
console.log('Summon added:', addResult)
|
console.log('Summon added:', addResult)
|
||||||
itemAdded = true
|
itemAdded = true
|
||||||
|
|
||||||
// Update local state with the added summon
|
// Update local state with the added summon
|
||||||
summons = [...summons, {
|
summons = [
|
||||||
|
...summons,
|
||||||
|
{
|
||||||
id: addResult.grid_summon?.id || `temp-${Date.now()}`,
|
id: addResult.grid_summon?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -179,7 +191,8 @@
|
||||||
},
|
},
|
||||||
main: position === -1,
|
main: position === -1,
|
||||||
friend: position === 6
|
friend: position === 6
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
} else if (activeTab === GridType.Character) {
|
} else if (activeTab === GridType.Character) {
|
||||||
// Use selectedSlot if available, otherwise default to first slot
|
// Use selectedSlot if available, otherwise default to first slot
|
||||||
if (selectedSlot === null) position = 0
|
if (selectedSlot === null) position = 0
|
||||||
|
|
@ -193,7 +206,9 @@
|
||||||
itemAdded = true
|
itemAdded = true
|
||||||
|
|
||||||
// Update local state with the added character
|
// Update local state with the added character
|
||||||
characters = [...characters, {
|
characters = [
|
||||||
|
...characters,
|
||||||
|
{
|
||||||
id: addResult.grid_character?.id || `temp-${Date.now()}`,
|
id: addResult.grid_character?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -201,7 +216,8 @@
|
||||||
name: firstItem.name,
|
name: firstItem.name,
|
||||||
element: firstItem.element
|
element: firstItem.element
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
|
|
||||||
|
|
@ -248,7 +264,8 @@
|
||||||
} else if (error.errors && typeof error.errors === 'object') {
|
} else if (error.errors && typeof error.errors === 'object') {
|
||||||
// Rails-style validation errors
|
// Rails-style validation errors
|
||||||
errorDetails = Object.entries(error.errors).map(
|
errorDetails = Object.entries(error.errors).map(
|
||||||
([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`
|
([field, messages]) =>
|
||||||
|
`${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
errorDetails = []
|
errorDetails = []
|
||||||
|
|
@ -268,13 +285,18 @@
|
||||||
|
|
||||||
if (activeTab === GridType.Weapon) {
|
if (activeTab === GridType.Weapon) {
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (i === 0 && selectedSlot !== null && !weapons.find(w => w.position === selectedSlot)) {
|
if (
|
||||||
|
i === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!weapons.find((w) => w.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty weapon slot
|
// Find next empty weapon slot
|
||||||
const emptySlots = Array.from({ length: 10 }, (_, i) => i - 1)
|
const emptySlots = Array.from({ length: 10 }, (_, i) => i - 1).filter(
|
||||||
.filter(i => !weapons.find(w => w.position === i))
|
(i) => !weapons.find((w) => w.position === i)
|
||||||
|
)
|
||||||
if (emptySlots.length === 0) return // Grid full
|
if (emptySlots.length === 0) return // Grid full
|
||||||
position = emptySlots[0]
|
position = emptySlots[0]
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +310,9 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add to local state
|
// Add to local state
|
||||||
weapons = [...weapons, {
|
weapons = [
|
||||||
|
...weapons,
|
||||||
|
{
|
||||||
id: response.grid_weapon?.id || `temp-${Date.now()}`,
|
id: response.grid_weapon?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -297,16 +321,21 @@
|
||||||
element: item.element
|
element: item.element
|
||||||
},
|
},
|
||||||
mainhand: position === -1
|
mainhand: position === -1
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
} else if (activeTab === GridType.Summon) {
|
} else if (activeTab === GridType.Summon) {
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (i === 0 && selectedSlot !== null && !summons.find(s => s.position === selectedSlot)) {
|
if (
|
||||||
|
i === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!summons.find((s) => s.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty summon slot
|
// Find next empty summon slot
|
||||||
const emptySlots = [-1, 0, 1, 2, 3, 6] // main, 4 grid slots, friend
|
const emptySlots = [-1, 0, 1, 2, 3, 6] // main, 4 grid slots, friend
|
||||||
.filter(i => !summons.find(s => s.position === i))
|
.filter((i) => !summons.find((s) => s.position === i))
|
||||||
if (emptySlots.length === 0) return // Grid full
|
if (emptySlots.length === 0) return // Grid full
|
||||||
position = emptySlots[0]
|
position = emptySlots[0]
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +349,9 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add to local state
|
// Add to local state
|
||||||
summons = [...summons, {
|
summons = [
|
||||||
|
...summons,
|
||||||
|
{
|
||||||
id: response.grid_summon?.id || `temp-${Date.now()}`,
|
id: response.grid_summon?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -330,16 +361,22 @@
|
||||||
},
|
},
|
||||||
main: position === -1,
|
main: position === -1,
|
||||||
friend: position === 6
|
friend: position === 6
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
} else if (activeTab === GridType.Character) {
|
} else if (activeTab === GridType.Character) {
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (i === 0 && selectedSlot !== null && !characters.find(c => c.position === selectedSlot)) {
|
if (
|
||||||
|
i === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!characters.find((c) => c.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty character slot
|
// Find next empty character slot
|
||||||
const emptySlots = Array.from({ length: 5 }, (_, i) => i)
|
const emptySlots = Array.from({ length: 5 }, (_, i) => i).filter(
|
||||||
.filter(i => !characters.find(c => c.position === i))
|
(i) => !characters.find((c) => c.position === i)
|
||||||
|
)
|
||||||
if (emptySlots.length === 0) return // Grid full
|
if (emptySlots.length === 0) return // Grid full
|
||||||
position = emptySlots[0]
|
position = emptySlots[0]
|
||||||
}
|
}
|
||||||
|
|
@ -353,7 +390,9 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add to local state
|
// Add to local state
|
||||||
characters = [...characters, {
|
characters = [
|
||||||
|
...characters,
|
||||||
|
{
|
||||||
id: response.grid_character?.id || `temp-${Date.now()}`,
|
id: response.grid_character?.id || `temp-${Date.now()}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -361,7 +400,8 @@
|
||||||
name: item.name,
|
name: item.name,
|
||||||
element: item.element
|
element: item.element
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -377,17 +417,21 @@
|
||||||
if (activeTab === GridType.Weapon) {
|
if (activeTab === GridType.Weapon) {
|
||||||
// Add weapons to empty slots
|
// Add weapons to empty slots
|
||||||
const emptySlots = Array.from({ length: 10 }, (_, i) => i - 1) // -1 for mainhand, 0-8 for grid
|
const emptySlots = Array.from({ length: 10 }, (_, i) => i - 1) // -1 for mainhand, 0-8 for grid
|
||||||
.filter(i => !weapons.find(w => w.position === i))
|
.filter((i) => !weapons.find((w) => w.position === i))
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
let position: number
|
let position: number
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (index === 0 && selectedSlot !== null && !weapons.find(w => w.position === selectedSlot)) {
|
if (
|
||||||
|
index === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!weapons.find((w) => w.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty slot
|
// Find next empty slot
|
||||||
const availableSlots = emptySlots.filter(s => !weapons.find(w => w.position === s))
|
const availableSlots = emptySlots.filter((s) => !weapons.find((w) => w.position === s))
|
||||||
if (availableSlots.length === 0) return
|
if (availableSlots.length === 0) return
|
||||||
position = availableSlots[0]
|
position = availableSlots[0]
|
||||||
}
|
}
|
||||||
|
|
@ -409,22 +453,30 @@
|
||||||
} else if (activeTab === GridType.Summon) {
|
} else if (activeTab === GridType.Summon) {
|
||||||
// Add summons to empty slots
|
// Add summons to empty slots
|
||||||
const emptySlots = [-1, 0, 1, 2, 3, 6] // main, 4 grid slots, friend
|
const emptySlots = [-1, 0, 1, 2, 3, 6] // main, 4 grid slots, friend
|
||||||
.filter(i => !summons.find(s => s.position === i))
|
.filter((i) => !summons.find((s) => s.position === i))
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
let position: number
|
let position: number
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (index === 0 && selectedSlot !== null && !summons.find(s => s.position === selectedSlot)) {
|
if (
|
||||||
|
index === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!summons.find((s) => s.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty slot
|
// Find next empty slot
|
||||||
const availableSlots = emptySlots.filter(s => !summons.find(sum => sum.position === s))
|
const availableSlots = emptySlots.filter(
|
||||||
|
(s) => !summons.find((sum) => sum.position === s)
|
||||||
|
)
|
||||||
if (availableSlots.length === 0) return
|
if (availableSlots.length === 0) return
|
||||||
position = availableSlots[0]
|
position = availableSlots[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
summons = [...summons, {
|
summons = [
|
||||||
|
...summons,
|
||||||
|
{
|
||||||
id: `temp-${Date.now()}-${index}`,
|
id: `temp-${Date.now()}-${index}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -434,27 +486,35 @@
|
||||||
},
|
},
|
||||||
main: position === -1,
|
main: position === -1,
|
||||||
friend: position === 6
|
friend: position === 6
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
} else if (activeTab === GridType.Character) {
|
} else if (activeTab === GridType.Character) {
|
||||||
// Add characters to empty slots
|
// Add characters to empty slots
|
||||||
const emptySlots = Array.from({ length: 5 }, (_, i) => i)
|
const emptySlots = Array.from({ length: 5 }, (_, i) => i).filter(
|
||||||
.filter(i => !characters.find(c => c.position === i))
|
(i) => !characters.find((c) => c.position === i)
|
||||||
|
)
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
let position: number
|
let position: number
|
||||||
// Use selectedSlot for first item if available
|
// Use selectedSlot for first item if available
|
||||||
if (index === 0 && selectedSlot !== null && !characters.find(c => c.position === selectedSlot)) {
|
if (
|
||||||
|
index === 0 &&
|
||||||
|
selectedSlot !== null &&
|
||||||
|
!characters.find((c) => c.position === selectedSlot)
|
||||||
|
) {
|
||||||
position = selectedSlot
|
position = selectedSlot
|
||||||
selectedSlot = null // Reset after using
|
selectedSlot = null // Reset after using
|
||||||
} else {
|
} else {
|
||||||
// Find next empty slot
|
// Find next empty slot
|
||||||
const availableSlots = emptySlots.filter(s => !characters.find(c => c.position === s))
|
const availableSlots = emptySlots.filter((s) => !characters.find((c) => c.position === s))
|
||||||
if (availableSlots.length === 0) return
|
if (availableSlots.length === 0) return
|
||||||
position = availableSlots[0]
|
position = availableSlots[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
characters = [...characters, {
|
characters = [
|
||||||
|
...characters,
|
||||||
|
{
|
||||||
id: `temp-${Date.now()}-${index}`,
|
id: `temp-${Date.now()}-${index}`,
|
||||||
position,
|
position,
|
||||||
object: {
|
object: {
|
||||||
|
|
@ -462,7 +522,8 @@
|
||||||
name: item.name,
|
name: item.name,
|
||||||
element: item.element
|
element: item.element
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -470,19 +531,19 @@
|
||||||
// Remove functions
|
// Remove functions
|
||||||
function removeWeapon(itemId: string) {
|
function removeWeapon(itemId: string) {
|
||||||
console.log('Removing weapon:', itemId)
|
console.log('Removing weapon:', itemId)
|
||||||
weapons = weapons.filter(w => w.id !== itemId)
|
weapons = weapons.filter((w) => w.id !== itemId)
|
||||||
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSummon(itemId: string) {
|
function removeSummon(itemId: string) {
|
||||||
console.log('Removing summon:', itemId)
|
console.log('Removing summon:', itemId)
|
||||||
summons = summons.filter(s => s.id !== itemId)
|
summons = summons.filter((s) => s.id !== itemId)
|
||||||
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCharacter(itemId: string) {
|
function removeCharacter(itemId: string) {
|
||||||
console.log('Removing character:', itemId)
|
console.log('Removing character:', itemId)
|
||||||
characters = characters.filter(c => c.id !== itemId)
|
characters = characters.filter((c) => c.id !== itemId)
|
||||||
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,24 +562,36 @@
|
||||||
removeWeapon: (partyId: string, itemId: string) => removeWeapon(itemId),
|
removeWeapon: (partyId: string, itemId: string) => removeWeapon(itemId),
|
||||||
removeSummon: (partyId: string, itemId: string) => removeSummon(itemId),
|
removeSummon: (partyId: string, itemId: string) => removeSummon(itemId),
|
||||||
removeCharacter: (partyId: string, itemId: string) => removeCharacter(itemId),
|
removeCharacter: (partyId: string, itemId: string) => removeCharacter(itemId),
|
||||||
addWeapon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
addWeapon: () =>
|
||||||
addSummon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
||||||
addCharacter: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
addSummon: () =>
|
||||||
replaceWeapon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
||||||
replaceSummon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
addCharacter: () =>
|
||||||
replaceCharacter: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } })
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
||||||
|
replaceWeapon: () =>
|
||||||
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
||||||
|
replaceSummon: () =>
|
||||||
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }),
|
||||||
|
replaceCharacter: () =>
|
||||||
|
Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } })
|
||||||
},
|
},
|
||||||
partyService: { getEditKey: () => null }
|
partyService: { getEditKey: () => null }
|
||||||
},
|
},
|
||||||
openPicker: (opts: { type: 'weapon' | 'summon' | 'character'; position: number; item?: any }) => {
|
openPicker: (opts: {
|
||||||
|
type: 'weapon' | 'summon' | 'character'
|
||||||
|
position: number
|
||||||
|
item?: any
|
||||||
|
}) => {
|
||||||
selectedSlot = opts.position
|
selectedSlot = opts.position
|
||||||
openSearchSidebar({
|
openSearchSidebar({
|
||||||
type: opts.type,
|
type: opts.type,
|
||||||
onAddItems: handleAddItems,
|
onAddItems: handleAddItems,
|
||||||
canAddMore: !isGridFull(
|
canAddMore: !isGridFull(
|
||||||
opts.type === 'weapon' ? GridType.Weapon :
|
opts.type === 'weapon'
|
||||||
opts.type === 'summon' ? GridType.Summon :
|
? GridType.Weapon
|
||||||
GridType.Character
|
: opts.type === 'summon'
|
||||||
|
? GridType.Summon
|
||||||
|
: GridType.Character
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -533,13 +606,20 @@
|
||||||
<h1>Create a new team</h1>
|
<h1>Create a new team</h1>
|
||||||
<p class="description">Search and click items to add them to your grid</p>
|
<p class="description">Search and click items to add them to your grid</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="toggle-sidebar" on:click={() => openSearchSidebar({
|
<button
|
||||||
type: activeTab === GridType.Weapon ? 'weapon' :
|
class="toggle-sidebar"
|
||||||
activeTab === GridType.Summon ? 'summon' :
|
on:click={() =>
|
||||||
'character',
|
openSearchSidebar({
|
||||||
|
type:
|
||||||
|
activeTab === GridType.Weapon
|
||||||
|
? 'weapon'
|
||||||
|
: activeTab === GridType.Summon
|
||||||
|
? 'summon'
|
||||||
|
: 'character',
|
||||||
onAddItems: handleAddItems,
|
onAddItems: handleAddItems,
|
||||||
canAddMore: !isGridFull(activeTab)
|
canAddMore: !isGridFull(activeTab)
|
||||||
})}>
|
})}
|
||||||
|
>
|
||||||
Open Search
|
Open Search
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -592,9 +672,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<Dialog.Close class="dialog-button">
|
<Dialog.Close class="dialog-button">OK</Dialog.Close>
|
||||||
OK
|
|
||||||
</Dialog.Close>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
|
|
@ -622,7 +700,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.party-info h1 {
|
.party-info h1 {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ $desktop: 1920px;
|
||||||
$laptop: 1280px;
|
$laptop: 1280px;
|
||||||
$tablet: 768px;
|
$tablet: 768px;
|
||||||
$phone: 375px;
|
$phone: 375px;
|
||||||
$grid-width: 720px;
|
$grid-width: 780px;
|
||||||
|
|
||||||
$character-rep-height: 111px;
|
$character-rep-height: 111px;
|
||||||
$summon-rep-height: 117px;
|
$summon-rep-height: 117px;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue