add context menus to units and party
This commit is contained in:
parent
da4c3d09f9
commit
e26b5c2e20
4 changed files with 459 additions and 112 deletions
|
|
@ -10,6 +10,7 @@
|
|||
import CharacterGrid from '$lib/components/grids/CharacterGrid.svelte'
|
||||
import SearchSidebar from '$lib/components/panels/SearchSidebar.svelte'
|
||||
import type { SearchResult } from '$lib/api/resources/search'
|
||||
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||
|
||||
interface Props {
|
||||
party?: Party
|
||||
|
|
@ -42,6 +43,8 @@
|
|||
let pickerOpen = $state(false)
|
||||
let pickerTitle = $state('Search')
|
||||
let selectedSlot = $state<number>(0)
|
||||
let editDialogOpen = $state(false)
|
||||
let editingTitle = $state('')
|
||||
|
||||
// Services
|
||||
const partyService = new PartyService(fetch)
|
||||
|
|
@ -93,23 +96,49 @@
|
|||
function selectTab(key: typeof tabs[number]['key']) {
|
||||
activeTab = key
|
||||
}
|
||||
|
||||
|
||||
// Edit dialog functions
|
||||
function openEditDialog() {
|
||||
if (!canEdit()) return
|
||||
editingTitle = party.name || ''
|
||||
editDialogOpen = true
|
||||
}
|
||||
|
||||
async function savePartyTitle() {
|
||||
if (!canEdit()) return
|
||||
|
||||
try {
|
||||
loading = true
|
||||
error = null
|
||||
|
||||
// Update party title via API
|
||||
const updated = await updatePartyDetails({ name: editingTitle })
|
||||
if (updated) {
|
||||
party = updated
|
||||
editDialogOpen = false
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to update party title'
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// Party operations
|
||||
async function updatePartyDetails(updates: Partial<Party>) {
|
||||
if (!canEdit()) return
|
||||
|
||||
if (!canEdit()) return null
|
||||
|
||||
loading = true
|
||||
error = null
|
||||
|
||||
|
||||
try {
|
||||
const updated = await partyService.update(
|
||||
party.id,
|
||||
updates,
|
||||
editKey || undefined
|
||||
)
|
||||
// Use apiClient for client-side updates (handles edit keys automatically)
|
||||
const updated = await apiClient.updateParty(party.id, updates)
|
||||
party = updated
|
||||
return updated
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to update party'
|
||||
return null
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
|
@ -263,22 +292,37 @@
|
|||
// Create client-side wrappers for grid operations using API client
|
||||
const clientGridService = {
|
||||
async removeWeapon(partyId: string, gridWeaponId: string, _editKey?: string) {
|
||||
await apiClient.removeWeapon(partyId, gridWeaponId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
try {
|
||||
await apiClient.removeWeapon(partyId, gridWeaponId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to remove weapon:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async removeSummon(partyId: string, gridSummonId: string, _editKey?: string) {
|
||||
await apiClient.removeSummon(partyId, gridSummonId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
try {
|
||||
await apiClient.removeSummon(partyId, gridSummonId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to remove summon:', err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
async removeCharacter(partyId: string, gridCharacterId: string, _editKey?: string) {
|
||||
await apiClient.removeCharacter(partyId, gridCharacterId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
try {
|
||||
await apiClient.removeCharacter(partyId, gridCharacterId)
|
||||
// Reload party data
|
||||
const updated = await partyService.getByShortcode(party.shortcode)
|
||||
return updated
|
||||
} catch (err) {
|
||||
console.error('Failed to remove character:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -314,23 +358,34 @@
|
|||
<p class="description">{party.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="party-actions">
|
||||
{#if canEdit()}
|
||||
<button
|
||||
class="edit-btn"
|
||||
onclick={openEditDialog}
|
||||
disabled={loading}
|
||||
aria-label="Edit party details"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if authUserId}
|
||||
<button
|
||||
<button
|
||||
class="favorite-btn"
|
||||
class:favorited={party.favorited}
|
||||
on:click={toggleFavorite}
|
||||
onclick={toggleFavorite}
|
||||
disabled={loading}
|
||||
aria-label={party.favorited ? 'Remove from favorites' : 'Add to favorites'}
|
||||
>
|
||||
{party.favorited ? '★' : '☆'}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
class="remix-btn"
|
||||
on:click={remixParty}
|
||||
onclick={remixParty}
|
||||
disabled={loading}
|
||||
aria-label="Remix this party"
|
||||
>
|
||||
|
|
@ -366,7 +421,7 @@
|
|||
class="tab-btn"
|
||||
aria-pressed={activeTab === t.key}
|
||||
class:active={activeTab === t.key}
|
||||
on:click={() => selectTab(t.key)}
|
||||
onclick={() => selectTab(t.key)}
|
||||
>
|
||||
{t.label}
|
||||
{#if t.count > 0}
|
||||
|
|
@ -410,6 +465,39 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<Dialog bind:open={editDialogOpen} title="Edit Party Details">
|
||||
{#snippet children()}
|
||||
<div class="edit-form">
|
||||
<label for="party-title">Party Title</label>
|
||||
<input
|
||||
id="party-title"
|
||||
type="text"
|
||||
bind:value={editingTitle}
|
||||
placeholder="Enter party title..."
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet footer()}
|
||||
<button
|
||||
class="btn-secondary"
|
||||
onclick={() => (editDialogOpen = false)}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<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; }
|
||||
|
|
@ -437,6 +525,7 @@
|
|||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.edit-btn,
|
||||
.favorite-btn,
|
||||
.remix-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
|
|
@ -446,6 +535,10 @@
|
|||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.favorite-btn {
|
||||
font-size: 1.2rem;
|
||||
|
|
@ -456,11 +549,13 @@
|
|||
color: gold;
|
||||
}
|
||||
|
||||
.edit-btn:hover,
|
||||
.favorite-btn:hover,
|
||||
.remix-btn:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
|
||||
.edit-btn:disabled,
|
||||
.favorite-btn:disabled,
|
||||
.remix-btn:disabled {
|
||||
opacity: 0.5;
|
||||
|
|
@ -561,4 +656,75 @@
|
|||
max-width: 400px;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
/* Edit form styles */
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.edit-form label {
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary, #666);
|
||||
}
|
||||
|
||||
.edit-form input {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.edit-form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--focus-ring, #3366ff);
|
||||
}
|
||||
|
||||
.edit-form input:disabled {
|
||||
background: #f5f5f5;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Dialog buttons */
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--button-primary-bg, #3366ff);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--button-primary-hover, #2855cc);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--text-primary, #333);
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn-secondary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import type { Party } from '$lib/types/api/party'
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
item?: GridCharacter
|
||||
|
|
@ -55,35 +57,92 @@
|
|||
|
||||
async function remove() {
|
||||
if (!item?.id) return
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeCharacter(party.id, item.id as any, editKey || undefined)
|
||||
ctx.updateParty(updated)
|
||||
try {
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeCharacter(party.id, item.id as any, editKey || undefined)
|
||||
if (updated) {
|
||||
ctx.updateParty(updated)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error removing character:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetails() {
|
||||
// TODO: Implement view details modal
|
||||
console.log('View details for:', item)
|
||||
}
|
||||
|
||||
function replace() {
|
||||
if (ctx?.openPicker) {
|
||||
ctx.openPicker({ type: 'character', position, item })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="unit" class:empty={!item}>
|
||||
{#key (item ? (item as any).id ?? position : `empty-${position}`)}
|
||||
<div
|
||||
class="frame character cell"
|
||||
class:editable={ctx?.canEdit()}
|
||||
on:click={() => ctx?.openPicker && ctx.openPicker({ type: 'character', position, item })}
|
||||
>
|
||||
<img class="image" class:placeholder={!item || !(item?.character?.granblueId || (item as any)?.object?.granblueId)} alt={item ? displayName(item?.character || (item as any)?.object) : ''} src={imageUrl()} />
|
||||
{#if !item && ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if ctx.canEdit() && item?.id}
|
||||
<div class="actions">
|
||||
<button class="remove" title="Remove" on:click|stopPropagation={remove}>×</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
{#snippet children()}
|
||||
{#key (item as any).id ?? position}
|
||||
<div
|
||||
class="frame character cell"
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => ctx?.canEdit() && replace()}
|
||||
>
|
||||
<img
|
||||
class="image"
|
||||
class:placeholder={!(item?.character?.granblueId || (item as any)?.object?.granblueId)}
|
||||
alt={displayName(item?.character || (item as any)?.object)}
|
||||
src={imageUrl()}
|
||||
/>
|
||||
{#if ctx?.canEdit() && item?.id}
|
||||
<div class="actions">
|
||||
<button class="remove" title="Remove" onclick={(e) => { e.stopPropagation(); remove() }}>×</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
<div
|
||||
class="frame character cell"
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'character', position, item })}
|
||||
>
|
||||
<img
|
||||
class="image placeholder"
|
||||
alt=""
|
||||
src="/images/placeholders/placeholder-weapon-grid.png"
|
||||
/>
|
||||
{#if ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
<div class="name">{item ? displayName(item?.character || (item as any)?.object) : ''}</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import type { Party } from '$lib/types/api/party'
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
item?: GridSummon
|
||||
|
|
@ -49,44 +51,104 @@
|
|||
|
||||
async function remove() {
|
||||
if (!item?.id) return
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeSummon(party.id, item.id as any, editKey || undefined)
|
||||
ctx.updateParty(updated)
|
||||
try {
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeSummon(party.id, item.id as any, editKey || undefined)
|
||||
if (updated) {
|
||||
ctx.updateParty(updated)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error removing summon:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetails() {
|
||||
// TODO: Implement view details modal
|
||||
console.log('View details for:', item)
|
||||
}
|
||||
|
||||
function replace() {
|
||||
if (ctx?.openPicker) {
|
||||
ctx.openPicker({ type: 'summon', position, item })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="unit" class:empty={!item}>
|
||||
{#key (item ? (item as any).id ?? position : `empty-${position}`)}
|
||||
<div
|
||||
class="frame summon"
|
||||
class:main={item?.main || position === -1}
|
||||
class:friend={item?.friend || position === 6}
|
||||
class:cell={!((item?.main || position === -1) || (item?.friend || position === 6))}
|
||||
class:editable={ctx?.canEdit()}
|
||||
on:click={() => ctx?.openPicker && ctx.openPicker({ type: 'summon', position, item })}
|
||||
>
|
||||
<img class="image" class:placeholder={!item || !(item?.summon?.granblueId || (item as any)?.object?.granblueId)} alt={item ? displayName(item?.summon || (item as any)?.object) : ''} src={imageUrl()} />
|
||||
{#if !item && ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if ctx.canEdit() && item?.id}
|
||||
<div class="actions">
|
||||
<button class="remove" title="Remove" on:click|stopPropagation={remove}>×</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item?.main || position === -1}
|
||||
<span class="badge">Main</span>
|
||||
{/if}
|
||||
{#if item?.friend || position === 6}
|
||||
<span class="badge" style="left:auto; right:6px">Friend</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
{#snippet children()}
|
||||
{#key (item as any).id ?? position}
|
||||
<div
|
||||
class="frame summon"
|
||||
class:main={item?.main || position === -1}
|
||||
class:friend={item?.friend || position === 6}
|
||||
class:cell={!((item?.main || position === -1) || (item?.friend || position === 6))}
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => ctx?.canEdit() && replace()}
|
||||
>
|
||||
<img
|
||||
class="image"
|
||||
class:placeholder={!(item?.summon?.granblueId || (item as any)?.object?.granblueId)}
|
||||
alt={displayName(item?.summon || (item as any)?.object)}
|
||||
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}
|
||||
{#if item?.friend || position === 6}
|
||||
<span class="badge" style="left:auto; right:6px">Friend</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
<div
|
||||
class="frame summon"
|
||||
class:main={position === -1}
|
||||
class:friend={position === 6}
|
||||
class:cell={!(position === -1 || position === 6)}
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'summon', position, item })}
|
||||
>
|
||||
<img
|
||||
class="image placeholder"
|
||||
alt=""
|
||||
src={position === -1 || position === 6 ? '/images/placeholders/placeholder-summon-main.png' : '/images/placeholders/placeholder-summon-sub.png'}
|
||||
/>
|
||||
{#if ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
<div class="name">{item ? displayName(item?.summon || (item as any)?.object) : ''}</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import type { Party } from '$lib/types/api/party'
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
item?: GridWeapon
|
||||
|
|
@ -57,40 +59,98 @@
|
|||
|
||||
async function remove() {
|
||||
if (!item?.id) return
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeWeapon(party.id, item.id as any, editKey || undefined)
|
||||
ctx.updateParty(updated)
|
||||
try {
|
||||
const party = ctx.getParty()
|
||||
const editKey = ctx.getEditKey()
|
||||
const updated = await ctx.services.gridService.removeWeapon(party.id, item.id as any, editKey || undefined)
|
||||
if (updated) {
|
||||
ctx.updateParty(updated)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error removing weapon:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetails() {
|
||||
// TODO: Implement view details modal
|
||||
console.log('View details for:', item)
|
||||
}
|
||||
|
||||
function replace() {
|
||||
if (ctx?.openPicker) {
|
||||
ctx.openPicker({ type: 'weapon', position, item })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="unit" class:empty={!item}>
|
||||
{#key (item ? (item as any).id ?? position : `empty-${position}`)}
|
||||
<div
|
||||
class="frame weapon"
|
||||
class:main={item?.mainhand || position === -1}
|
||||
class:cell={!(item?.mainhand || position === -1)}
|
||||
class:editable={ctx?.canEdit()}
|
||||
on:click={() => ctx?.openPicker && ctx.openPicker({ type: 'weapon', position, item })}
|
||||
>
|
||||
<img class="image" class:placeholder={!item || !(item?.weapon?.granblueId || (item as any)?.object?.granblueId)} alt={item ? displayName(item?.weapon || (item as any)?.object) : ''} src={imageUrl()} />
|
||||
{#if !item && ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if ctx.canEdit() && item?.id}
|
||||
<div class="actions">
|
||||
<button class="remove" title="Remove" on:click|stopPropagation={remove}>×</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item?.mainhand || position === -1}
|
||||
<span class="badge">Main</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{#if item}
|
||||
<ContextMenu>
|
||||
{#snippet children()}
|
||||
{#key (item as any).id ?? position}
|
||||
<div
|
||||
class="frame weapon"
|
||||
class:main={item?.mainhand || position === -1}
|
||||
class:cell={!(item?.mainhand || position === -1)}
|
||||
class:editable={ctx?.canEdit()}
|
||||
>
|
||||
<img
|
||||
class="image"
|
||||
class:placeholder={!(item?.weapon?.granblueId || (item as any)?.object?.granblueId)}
|
||||
alt={displayName(item?.weapon || (item as any)?.object)}
|
||||
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?.mainhand || position === -1}
|
||||
<span class="badge">Main</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/snippet}
|
||||
|
||||
{#snippet menu()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={viewDetails}>
|
||||
View Details
|
||||
</ContextMenuBase.Item>
|
||||
{#if ctx?.canEdit()}
|
||||
<ContextMenuBase.Item class="context-menu-item" onclick={replace}>
|
||||
Replace
|
||||
</ContextMenuBase.Item>
|
||||
<ContextMenuBase.Separator class="context-menu-separator" />
|
||||
<ContextMenuBase.Item class="context-menu-item danger" onclick={remove}>
|
||||
Remove
|
||||
</ContextMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ContextMenu>
|
||||
{:else}
|
||||
{#key `empty-${position}`}
|
||||
<div
|
||||
class="frame weapon"
|
||||
class:main={position === -1}
|
||||
class:cell={position !== -1}
|
||||
class:editable={ctx?.canEdit()}
|
||||
onclick={() => ctx?.canEdit() && ctx?.openPicker && ctx.openPicker({ type: 'weapon', position, item })}
|
||||
>
|
||||
<img
|
||||
class="image placeholder"
|
||||
alt=""
|
||||
src={position === -1 ? '/images/placeholders/placeholder-weapon-main.png' : '/images/placeholders/placeholder-weapon-grid.png'}
|
||||
/>
|
||||
{#if ctx?.canEdit()}
|
||||
<span class="icon">
|
||||
<Icon name="plus" size={24} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
<div class="name">{item ? displayName(item?.weapon || (item as any)?.object) : ''}</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue