Expand website and game trackers
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
This commit is contained in:
183
src/lib/game-tracker/CatchEditor.svelte
Normal file
183
src/lib/game-tracker/CatchEditor.svelte
Normal file
@@ -0,0 +1,183 @@
|
||||
<script lang="ts">
|
||||
import { pokemonImage } from '$lib/game-tracker/game-art';
|
||||
import { enhance } from '$app/forms';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { stages, obtainedMethods, type Stage } from './journal';
|
||||
import type { PokemonCatch } from '$lib/server/db/schema/tracker-journal';
|
||||
let {
|
||||
open = $bindable(false),
|
||||
species,
|
||||
gameId,
|
||||
record = null,
|
||||
stage = 'story',
|
||||
location = '',
|
||||
signedIn,
|
||||
ondelete,
|
||||
ondetails
|
||||
}: {
|
||||
open?: boolean;
|
||||
species: { id: number; number: number; name: string };
|
||||
gameId: number;
|
||||
record?: PokemonCatch | null;
|
||||
stage?: Stage;
|
||||
location?: string;
|
||||
signedIn: boolean;
|
||||
ondelete?: () => void;
|
||||
ondetails?: () => void;
|
||||
} = $props();
|
||||
let message = $state('');
|
||||
let saving = $state(false);
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-h-[90dvh] overflow-y-auto sm:max-w-2xl">
|
||||
<Dialog.Header
|
||||
><Dialog.Title>{record ? 'Meet' : 'Register'} {record?.nickname || species.name}</Dialog.Title
|
||||
><Dialog.Description
|
||||
>#{species.number} · {species.name}. Keep this Pokémon’s story, moves and party position
|
||||
together.</Dialog.Description
|
||||
></Dialog.Header
|
||||
>
|
||||
{#if ondetails}<Button variant="link" class="justify-self-start px-0" onclick={ondetails}
|
||||
>All catches, moves and species research →</Button
|
||||
>{/if}
|
||||
{#if message}<p role="alert" class="text-destructive">{message}</p>{/if}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/saveCatch"
|
||||
class="space-y-5"
|
||||
use:enhance={() => {
|
||||
saving = true;
|
||||
message = '';
|
||||
return async ({ result, update }) => {
|
||||
await update({ reset: false });
|
||||
saving = false;
|
||||
if (result.type === 'success') open = false;
|
||||
else
|
||||
message =
|
||||
result.type === 'failure'
|
||||
? String(result.data?.message ?? 'Could not save.')
|
||||
: 'Could not save. Try again.';
|
||||
};
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="gameId" value={gameId} /><input
|
||||
type="hidden"
|
||||
name="pokemonId"
|
||||
value={species.id}
|
||||
/>{#if record}<input type="hidden" name="id" value={record.id} />{/if}
|
||||
<div class="flex items-center gap-4 rounded-xl bg-muted/40 p-3">
|
||||
<img
|
||||
src={pokemonImage(species.number, record?.shiny ? 'shiny' : 'sprite')}
|
||||
alt={species.name}
|
||||
width="80"
|
||||
height="80"
|
||||
/>
|
||||
<div class="grid flex-1 grid-cols-[1fr_5rem] gap-3">
|
||||
<label
|
||||
>Nickname<Input
|
||||
name="nickname"
|
||||
value={record?.nickname ?? ''}
|
||||
maxlength={50}
|
||||
placeholder={species.name}
|
||||
/></label
|
||||
><label
|
||||
>Level<Input
|
||||
type="number"
|
||||
name="level"
|
||||
min={1}
|
||||
max={100}
|
||||
required
|
||||
value={record?.level ?? 5}
|
||||
/></label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<label
|
||||
>Party position<NativeSelect
|
||||
name="partySlot"
|
||||
value={record?.partySlot ?? ''}
|
||||
class="mt-1 w-full"
|
||||
><option value="">In the PC</option>{#each [1, 2, 3, 4, 5, 6] as slot (slot)}<option
|
||||
value={slot}>Team slot {slot}</option
|
||||
>{/each}</NativeSelect
|
||||
><span class="text-xs text-muted-foreground"
|
||||
>An occupied slot moves its current Pokémon to the PC.</span
|
||||
></label
|
||||
><label
|
||||
>Obtained by<NativeSelect
|
||||
name="obtained"
|
||||
value={record?.obtained ?? 'caught'}
|
||||
class="mt-1 w-full"
|
||||
>{#each obtainedMethods as method (method)}<option value={method}>{method}</option
|
||||
>{/each}</NativeSelect
|
||||
></label
|
||||
>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend class="mb-2 font-medium">Current moves</legend>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
{#each [0, 1, 2, 3] as slot (slot)}<Input
|
||||
name="moves"
|
||||
aria-label={`Move ${slot + 1}`}
|
||||
placeholder={`Move ${slot + 1}`}
|
||||
value={record?.moves[slot] ?? ''}
|
||||
maxlength={60}
|
||||
/>{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<label>Nature<Input name="nature" value={record?.nature ?? ''} maxlength={150} /></label
|
||||
><label>Ability<Input name="ability" value={record?.ability ?? ''} maxlength={150} /></label
|
||||
><label>Poké Ball<Input name="ball" value={record?.ball ?? ''} maxlength={150} /></label
|
||||
><label
|
||||
>Caught on<Input
|
||||
type="date"
|
||||
name="caughtOn"
|
||||
value={record?.caughtOn ?? new Date().toISOString().slice(0, 10)}
|
||||
/></label
|
||||
><label
|
||||
>Location<Input
|
||||
name="location"
|
||||
value={record?.location ?? location}
|
||||
maxlength={150}
|
||||
/></label
|
||||
><label
|
||||
>Collection stage<NativeSelect name="stage" value={record?.stage ?? stage} class="w-full"
|
||||
>{#each stages as s (s.value)}<option value={s.value}>{s.label}</option
|
||||
>{/each}</NativeSelect
|
||||
></label
|
||||
>
|
||||
</div>
|
||||
<label class="flex items-center gap-2"
|
||||
><Checkbox name="shiny" value="on" checked={record?.shiny ?? false} />Shiny Pokémon</label
|
||||
>
|
||||
<label class="block"
|
||||
>Notes<Textarea
|
||||
name="notes"
|
||||
value={record?.notes ?? ''}
|
||||
maxlength={2000}
|
||||
placeholder="A lucky encounter, a trade partner, a plan for evolution…"
|
||||
/></label
|
||||
>
|
||||
<Dialog.Footer
|
||||
><Button variant="outline" onclick={() => (open = false)}>Cancel</Button><Button
|
||||
type="submit"
|
||||
disabled={!signedIn || saving}
|
||||
>{saving ? 'Saving…' : record ? 'Save Pokémon' : 'Add to my collection'}</Button
|
||||
></Dialog.Footer
|
||||
>
|
||||
</form>
|
||||
{#if record && ondelete}<Button
|
||||
variant="ghost"
|
||||
class="justify-self-start text-destructive"
|
||||
onclick={ondelete}>Remove this catch record</Button
|
||||
>{/if}
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
Reference in New Issue
Block a user