wire up context menus to unit components
This commit is contained in:
parent
030c5916c7
commit
0ab2782697
3 changed files with 116 additions and 69 deletions
|
|
@ -5,13 +5,14 @@
|
|||
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'
|
||||
import { ContextMenu as ContextMenuBase, DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
|
||||
import { getCharacterImageWithPose } from '$lib/utils/images'
|
||||
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
||||
import { getJobPortraitUrl, Gender } from '$lib/utils/jobUtils'
|
||||
import perpetuityFilled from '$src/assets/icons/perpetuity/filled.svg'
|
||||
import perpetuityEmpty from '$src/assets/icons/perpetuity/empty.svg'
|
||||
import * as m from '$lib/paraglide/messages'
|
||||
|
||||
interface Props {
|
||||
item?: GridCharacter
|
||||
|
|
@ -141,35 +142,42 @@
|
|||
|
||||
<div class="unit" class:empty={!item}>
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
<ContextMenu showGearButton={true}>
|
||||
{#snippet children()}
|
||||
{#key item?.id ?? position}
|
||||
<div
|
||||
class="frame character cell"
|
||||
class:protagonist={position === 0}
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => viewDetails()}
|
||||
>
|
||||
{#if ctx?.canEdit()}
|
||||
<button
|
||||
class="perpetuity"
|
||||
class:active={item.perpetuity}
|
||||
onclick={togglePerpetuity}
|
||||
title={item.perpetuity ? 'Remove Perpetuity Ring' : 'Add Perpetuity Ring'}
|
||||
>
|
||||
<img class="perpetuity-icon filled" src={perpetuityFilled} alt="Perpetuity Ring" />
|
||||
{#if position !== 0}
|
||||
{#if ctx?.canEdit()}
|
||||
<button
|
||||
class="perpetuity"
|
||||
class:active={item.perpetuity}
|
||||
onclick={togglePerpetuity}
|
||||
title={item.perpetuity ? 'Remove Perpetuity Ring' : 'Add Perpetuity Ring'}
|
||||
>
|
||||
<img
|
||||
class="perpetuity-icon filled"
|
||||
src={perpetuityFilled}
|
||||
alt="Perpetuity Ring"
|
||||
/>
|
||||
<img
|
||||
class="perpetuity-icon empty"
|
||||
src={perpetuityEmpty}
|
||||
alt="Add Perpetuity Ring"
|
||||
/>
|
||||
</button>
|
||||
{:else if item.perpetuity}
|
||||
<img
|
||||
class="perpetuity-icon empty"
|
||||
src={perpetuityEmpty}
|
||||
alt="Add Perpetuity Ring"
|
||||
class="perpetuity static"
|
||||
src={perpetuityFilled}
|
||||
alt="Perpetuity Ring"
|
||||
title="Perpetuity Ring"
|
||||
/>
|
||||
</button>
|
||||
{:else if item.perpetuity}
|
||||
<img
|
||||
class="perpetuity static"
|
||||
src={perpetuityFilled}
|
||||
alt="Perpetuity Ring"
|
||||
title="Perpetuity Ring"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<img
|
||||
class="image"
|
||||
|
|
@ -182,20 +190,35 @@
|
|||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
{#snippet contextMenu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
{m.context_view_details()}
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
{m.context_replace()}
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
{m.context_remove()}
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet dropdownMenu()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={viewDetails}>
|
||||
{m.context_view_details()}
|
||||
</DropdownMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={replace}>
|
||||
{m.context_replace()}
|
||||
</DropdownMenuBase.Item>
|
||||
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item danger" onclick={remove}>
|
||||
{m.context_remove()}
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
|
|
@ -213,8 +236,8 @@
|
|||
class="image"
|
||||
class:placeholder={!isProtagonist || !job}
|
||||
class:protagonist={isProtagonist}
|
||||
alt={isProtagonist && job ? job.name.en : ""}
|
||||
src={isProtagonist ? imageUrl : "/images/placeholders/placeholder-weapon-grid.png"}
|
||||
alt={isProtagonist && job ? job.name.en : ''}
|
||||
src={isProtagonist ? imageUrl : '/images/placeholders/placeholder-weapon-grid.png'}
|
||||
/>
|
||||
{#if ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
|
|
@ -318,6 +341,13 @@
|
|||
|
||||
.frame.character.cell {
|
||||
@include rep.aspect(rep.$char-cell-w, rep.$char-cell-h);
|
||||
|
||||
&.protagonist {
|
||||
background-image: url('/images/relief.png'), linear-gradient(to right, #000, #484440, #000);
|
||||
background-size: cover;
|
||||
background-position: center -20px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@
|
|||
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'
|
||||
import { ContextMenu as ContextMenuBase, DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
|
||||
import { getSummonImage } from '$lib/features/database/detail/image'
|
||||
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
||||
import * as m from '$lib/paraglide/messages'
|
||||
|
||||
interface Props {
|
||||
item?: GridSummon
|
||||
|
|
@ -34,9 +35,10 @@
|
|||
}
|
||||
// Use $derived to ensure consistent computation between server and client
|
||||
let imageUrl = $derived.by(() => {
|
||||
// Check position first for main/friend summon determination
|
||||
const isMain = position === -1 || position === 6 || item?.main || item?.friend
|
||||
const variant = isMain ? 'main' : 'grid'
|
||||
// Only position -1 (main) and position 6 (friend) use main-sized images
|
||||
// All other positions (0-5) including subaura (4-5) use grid-sized images
|
||||
const isMainSized = position === -1 || position === 6 || item?.main || item?.friend
|
||||
const variant = isMainSized ? 'main' : 'grid'
|
||||
|
||||
return getSummonImage(item?.summon?.granblueId, variant)
|
||||
})
|
||||
|
|
@ -74,7 +76,7 @@
|
|||
|
||||
<div class="unit" class:empty={!item}>
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
<ContextMenu showGearButton={true}>
|
||||
{#snippet children()}
|
||||
{#key item?.id ?? position}
|
||||
<div
|
||||
|
|
@ -91,11 +93,6 @@
|
|||
alt={displayName(item?.summon)}
|
||||
src={imageUrl}
|
||||
/>
|
||||
{#if ctx?.canEdit() && item?.id}
|
||||
<div class="actions">
|
||||
<button class="remove" title="Remove" onclick={(e) => { e.stopPropagation(); remove() }}>×</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item?.main || position === -1}
|
||||
<span class="badge">Main</span>
|
||||
{/if}
|
||||
|
|
@ -106,20 +103,35 @@
|
|||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
{#snippet contextMenu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
{m.context_view_details()}
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
{m.context_replace()}
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
{m.context_remove()}
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet dropdownMenu()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={viewDetails}>
|
||||
{m.context_view_details()}
|
||||
</DropdownMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={replace}>
|
||||
{m.context_replace()}
|
||||
</DropdownMenuBase.Item>
|
||||
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item danger" onclick={remove}>
|
||||
{m.context_remove()}
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
|
|
@ -263,26 +275,6 @@
|
|||
color: $grey-50;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.remove {
|
||||
background: rgba(0,0,0,.6);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
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'
|
||||
import { ContextMenu as ContextMenuBase, DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
|
||||
import { getWeaponImage } from '$lib/features/database/detail/image'
|
||||
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
||||
import { getAwakeningImage, getWeaponKeyImages, getAxSkillImages } from '$lib/utils/modifiers'
|
||||
import * as m from '$lib/paraglide/messages'
|
||||
|
||||
interface Props {
|
||||
item?: GridWeapon
|
||||
|
|
@ -96,7 +97,7 @@
|
|||
|
||||
<div class="unit" class:empty={!item} class:extra={position >= 9}>
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
<ContextMenu showGearButton={true}>
|
||||
{#snippet children()}
|
||||
{#key item?.id ?? position}
|
||||
<div
|
||||
|
|
@ -134,20 +135,35 @@
|
|||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
{#snippet contextMenu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
{m.context_view_details()}
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
{m.context_replace()}
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
{m.context_remove()}
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet dropdownMenu()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={viewDetails}>
|
||||
{m.context_view_details()}
|
||||
</DropdownMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={replace}>
|
||||
{m.context_replace()}
|
||||
</DropdownMenuBase.Item>
|
||||
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||
<DropdownMenuBase.Item class="dropdown-menu-item danger" onclick={remove}>
|
||||
{m.context_remove()}
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
|
|
@ -218,6 +234,7 @@
|
|||
@use '$src/themes/colors' as *;
|
||||
@use '$src/themes/typography' as *;
|
||||
@use '$src/themes/spacing' as *;
|
||||
@use '$src/themes/layout' as *;
|
||||
@use '$src/themes/rep' as rep;
|
||||
|
||||
.unit {
|
||||
|
|
@ -265,13 +282,21 @@
|
|||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
&.editable:hover {
|
||||
opacity: 0.95;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transform: $scale-wide;
|
||||
}
|
||||
}
|
||||
|
||||
.frame.weapon.main {
|
||||
@include rep.aspect(rep.$weapon-main-w, rep.$weapon-main-h);
|
||||
|
||||
&.editable:hover {
|
||||
transform: $scale-tall;
|
||||
}
|
||||
}
|
||||
|
||||
.frame.weapon.main { @include rep.aspect(rep.$weapon-main-w, rep.$weapon-main-h); }
|
||||
.frame.weapon.cell { @include rep.aspect(rep.$weapon-cell-w, rep.$weapon-cell-h); }
|
||||
|
||||
.image {
|
||||
|
|
|
|||
Loading…
Reference in a new issue