|
<script lang="ts"> |
|
import type { PicletInstance } from '$lib/db/schema'; |
|
import PicletCard from './PicletCard.svelte'; |
|
import { moveToRoster } from '$lib/db/piclets'; |
|
|
|
interface Props { |
|
position: number; |
|
availablePiclets: PicletInstance[]; |
|
onClose: () => void; |
|
onAdded?: () => void; |
|
} |
|
|
|
let { position, availablePiclets, onClose, onAdded }: Props = $props(); |
|
let isAdding = $state(false); |
|
|
|
async function handleAddToRoster(piclet: PicletInstance) { |
|
if (!piclet.id || isAdding) return; |
|
|
|
isAdding = true; |
|
try { |
|
await moveToRoster(piclet.id, position); |
|
onAdded?.(); |
|
onClose(); |
|
} catch (err) { |
|
console.error('Failed to add to roster:', err); |
|
} finally { |
|
isAdding = false; |
|
} |
|
} |
|
</script> |
|
|
|
<div class="dialog-overlay" onclick={(e) => e.target === e.currentTarget && onClose()} onkeydown={(e) => e.key === 'Escape' && onClose()} role="button" tabindex="0" aria-label="Close dialog"> |
|
<div class="dialog-content"> |
|
<header class="dialog-header"> |
|
<h2>Add to Roster</h2> |
|
<button class="close-btn" onclick={onClose} aria-label="Close"> |
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
<path d="M6 18L18 6M6 6l12 12"></path> |
|
</svg> |
|
</button> |
|
</header> |
|
|
|
<div class="dialog-body"> |
|
{#if availablePiclets.length === 0} |
|
<div class="empty-state"> |
|
<p>No piclets available in storage.</p> |
|
<p class="hint">Scan objects to discover new piclets!</p> |
|
</div> |
|
{:else} |
|
<p class="instruction">Select a piclet to add to position {position + 1}:</p> |
|
<div class="piclets-grid"> |
|
{#each availablePiclets as piclet} |
|
<button |
|
class="piclet-option" |
|
onclick={() => handleAddToRoster(piclet)} |
|
disabled={isAdding} |
|
> |
|
<PicletCard piclet={piclet} size={100} /> |
|
</button> |
|
{/each} |
|
</div> |
|
{/if} |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
.dialog-overlay { |
|
position: fixed; |
|
inset: 0; |
|
background: rgba(0, 0, 0, 0.5); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
z-index: 1000; |
|
padding: 1rem; |
|
} |
|
|
|
.dialog-content { |
|
background: white; |
|
border-radius: 16px; |
|
width: 100%; |
|
max-width: 600px; |
|
max-height: 80vh; |
|
overflow: hidden; |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
|
|
.dialog-header { |
|
padding: 1rem; |
|
border-bottom: 1px solid #e5e5ea; |
|
position: relative; |
|
} |
|
|
|
.dialog-header h2 { |
|
margin: 0; |
|
text-align: center; |
|
font-size: 1.25rem; |
|
} |
|
|
|
.close-btn { |
|
position: absolute; |
|
top: 1rem; |
|
right: 1rem; |
|
background: none; |
|
border: none; |
|
padding: 0; |
|
width: 24px; |
|
height: 24px; |
|
cursor: pointer; |
|
color: #8e8e93; |
|
} |
|
|
|
.dialog-body { |
|
flex: 1; |
|
overflow-y: auto; |
|
padding: 1rem; |
|
} |
|
|
|
.empty-state { |
|
text-align: center; |
|
padding: 3rem 1rem; |
|
color: #666; |
|
} |
|
|
|
.empty-state p { |
|
margin: 0 0 0.5rem; |
|
} |
|
|
|
.hint { |
|
font-size: 0.875rem; |
|
color: #8e8e93; |
|
} |
|
|
|
.instruction { |
|
margin: 0 0 1rem; |
|
color: #666; |
|
text-align: center; |
|
} |
|
|
|
.piclets-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); |
|
gap: 1rem; |
|
justify-items: center; |
|
} |
|
|
|
.piclet-option { |
|
background: none; |
|
border: none; |
|
padding: 0; |
|
cursor: pointer; |
|
transition: transform 0.2s; |
|
} |
|
|
|
.piclet-option:not(:disabled):hover { |
|
transform: scale(1.05); |
|
} |
|
|
|
.piclet-option:not(:disabled):active { |
|
transform: scale(0.95); |
|
} |
|
|
|
.piclet-option:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
} |
|
</style> |