mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-17 19:12:03 +00:00
data(*): Add final data changes
This commit is contained in:
@@ -71,11 +71,12 @@
|
||||
|
||||
<!-- Email Input -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="signin-email">
|
||||
<span class="label-text font-medium">Email</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
id="signin-email"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
class="input input-bordered w-full pl-10"
|
||||
@@ -101,11 +102,12 @@
|
||||
|
||||
<!-- Password Input -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="signin-password">
|
||||
<span class="label-text font-medium">Password</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
id="signin-password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
class="input input-bordered w-full pl-10"
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
|
||||
<form class="card-body">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="signup-email">
|
||||
<span class="label-text">Email</span>
|
||||
</label>
|
||||
<input
|
||||
id="signup-email"
|
||||
type="email"
|
||||
placeholder="email"
|
||||
class="input input-bordered"
|
||||
@@ -50,10 +51,11 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="signup-password">
|
||||
<span class="label-text">Password</span>
|
||||
</label>
|
||||
<input
|
||||
id="signup-password"
|
||||
type="password"
|
||||
placeholder="password"
|
||||
class="input input-bordered"
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
</script>
|
||||
|
||||
<div class="relative inline-block">
|
||||
<div
|
||||
class="tooltip-trigger cursor-pointer"
|
||||
<button
|
||||
type="button"
|
||||
class="tooltip-trigger cursor-pointer bg-transparent border-0 p-0 appearance-none"
|
||||
aria-expanded={show}
|
||||
on:click={handleClick}
|
||||
on:mouseenter={() => (show = true)}
|
||||
on:mouseleave={() => (show = false)}
|
||||
>
|
||||
<slot name="hover-target" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if show}
|
||||
<div class="tooltip-content absolute z-10 mt-2 w-52">
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
<span class="font-semibold">Game:</span>
|
||||
{pokedex.gameScope}
|
||||
</div>
|
||||
{#if pokedex.dexScopes?.length}
|
||||
<div class="text-sm text-base-content/70">
|
||||
<span class="font-semibold">Dexes:</span>
|
||||
{pokedex.dexScopes.length}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-sm text-base-content/70">
|
||||
<span class="font-semibold">Scope:</span> All Games
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { CatchRecord } from '$lib/models/CatchRecord';
|
||||
import type { PokedexEntry } from '$lib/models/PokedexEntry';
|
||||
import type { CatchInformationItem, PokedexEntry } from '$lib/models/PokedexEntry';
|
||||
import PokemonSprite from '../PokemonSprite.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
|
||||
type UpdateCatchSource = 'toggle' | 'notes' | 'notes-blur';
|
||||
|
||||
const isCatchInformationItem = (
|
||||
value: string | CatchInformationItem
|
||||
): value is CatchInformationItem => typeof value !== 'string';
|
||||
|
||||
function updateCatchRecord(source: UpdateCatchSource) {
|
||||
dispatch('updateCatch', { pokedexEntry, catchRecord, source });
|
||||
}
|
||||
@@ -193,11 +197,15 @@
|
||||
<ul class="list-disc list-inside">
|
||||
{#each pokedexEntry.catchInformation as info}
|
||||
<li>
|
||||
<ul>
|
||||
<li><strong>Game:</strong> {info.game}</li>
|
||||
<li><strong>Location:</strong> {info.location}</li>
|
||||
<li><strong>Notes:</strong> {info.notes}</li>
|
||||
</ul>
|
||||
{#if isCatchInformationItem(info)}
|
||||
<ul>
|
||||
<li><strong>Game:</strong> {info.game}</li>
|
||||
<li><strong>Location:</strong> {info.location}</li>
|
||||
<li><strong>Notes:</strong> {info.notes}</li>
|
||||
</ul>
|
||||
{:else}
|
||||
{info}
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -9,36 +9,83 @@
|
||||
isShinyDex: false,
|
||||
isOriginDex: false,
|
||||
isFormDex: false,
|
||||
gameScope: null
|
||||
gameScope: null,
|
||||
dexScopes: []
|
||||
};
|
||||
export let mode: 'create' | 'edit' = 'create';
|
||||
export let onSubmit: () => void;
|
||||
export let onCancel: () => void;
|
||||
|
||||
let games: string[] = [];
|
||||
let loadingGames = true;
|
||||
type GameDex = {
|
||||
id: string;
|
||||
displayName: string;
|
||||
sortOrder: number;
|
||||
isDlc: boolean;
|
||||
parentDexId: string | null;
|
||||
};
|
||||
type GameSummary = {
|
||||
displayName: string;
|
||||
releaseYear: number;
|
||||
};
|
||||
|
||||
// Fetch available games from the database
|
||||
let gameDexes: Record<string, GameDex[]> = {};
|
||||
let gameOrder: string[] = [];
|
||||
let gameList: GameSummary[] = [];
|
||||
let availableDexes: GameDex[] = [];
|
||||
let loadingDexes = true;
|
||||
let lastGameScope: string | null = null;
|
||||
let dexScopesInitialized = false;
|
||||
let hasSeenGameScope = false;
|
||||
|
||||
// Fetch available game dexes from the database
|
||||
onMount(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/games');
|
||||
const response = await fetch('/api/game-dexes');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
games = data.games || [];
|
||||
gameDexes = data.gameDexes || {};
|
||||
gameOrder = data.gameOrder || Object.keys(gameDexes);
|
||||
gameList = data.games || [];
|
||||
} else {
|
||||
console.error('Failed to fetch games');
|
||||
console.error('Failed to fetch game dexes');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching games:', error);
|
||||
console.error('Error fetching game dexes:', error);
|
||||
} finally {
|
||||
loadingGames = false;
|
||||
loadingDexes = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Validation
|
||||
$: hasAtLeastOneType =
|
||||
pokedex.isLivingDex || pokedex.isShinyDex || pokedex.isOriginDex || pokedex.isFormDex;
|
||||
$: canSubmit = pokedex.name && pokedex.name.trim() !== '' && hasAtLeastOneType;
|
||||
$: hasDexScope =
|
||||
!pokedex.gameScope || (pokedex.dexScopes && pokedex.dexScopes.length > 0);
|
||||
$: canSubmit =
|
||||
pokedex.name && pokedex.name.trim() !== '' && hasAtLeastOneType && hasDexScope;
|
||||
|
||||
$: if (pokedex.gameScope !== lastGameScope) {
|
||||
const shouldResetDexes = mode === 'create' || hasSeenGameScope;
|
||||
lastGameScope = pokedex.gameScope || null;
|
||||
dexScopesInitialized = false;
|
||||
if (shouldResetDexes) {
|
||||
pokedex.dexScopes = [];
|
||||
}
|
||||
if (!pokedex.gameScope) {
|
||||
availableDexes = [];
|
||||
}
|
||||
hasSeenGameScope = true;
|
||||
}
|
||||
|
||||
$: if (!loadingDexes && pokedex.gameScope) {
|
||||
availableDexes = gameDexes[pokedex.gameScope] || [];
|
||||
if (!dexScopesInitialized) {
|
||||
if (!pokedex.dexScopes || pokedex.dexScopes.length === 0) {
|
||||
pokedex.dexScopes = availableDexes.map((dex) => dex.id);
|
||||
}
|
||||
dexScopesInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (canSubmit) {
|
||||
@@ -74,30 +121,32 @@
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Type(s)</span>
|
||||
<span class="label-text-alt text-error"
|
||||
>{!hasAtLeastOneType ? 'At least one type required' : ''}</span
|
||||
>
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isLivingDex} />
|
||||
<span class="label-text">Living Dex (one of each Pokémon)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isShinyDex} />
|
||||
<span class="label-text">Shiny Dex (shiny variants only)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isOriginDex} />
|
||||
<span class="label-text">Origin Dex (caught in native regions)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isFormDex} />
|
||||
<span class="label-text">Form Dex (all forms included)</span>
|
||||
</label>
|
||||
</div>
|
||||
<fieldset class="w-full">
|
||||
<legend class="label">
|
||||
<span class="label-text">Type(s)</span>
|
||||
<span class="label-text-alt text-error"
|
||||
>{!hasAtLeastOneType ? 'At least one type required' : ''}</span
|
||||
>
|
||||
</legend>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isLivingDex} />
|
||||
<span class="label-text">Living Dex (one of each Pokémon)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isShinyDex} />
|
||||
<span class="label-text">Shiny Dex (shiny variants only)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isOriginDex} />
|
||||
<span class="label-text">Origin Dex (caught in native regions)</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={pokedex.isFormDex} />
|
||||
<span class="label-text">Form Dex (all forms included)</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
@@ -108,19 +157,53 @@
|
||||
id="game-scope"
|
||||
class="select select-bordered"
|
||||
bind:value={pokedex.gameScope}
|
||||
disabled={loadingGames}
|
||||
disabled={loadingDexes}
|
||||
>
|
||||
<option value={null}>All Games</option>
|
||||
{#if loadingGames}
|
||||
{#if loadingDexes}
|
||||
<option disabled>Loading games...</option>
|
||||
{:else}
|
||||
{#each games as game}
|
||||
<option value={game}>{game}</option>
|
||||
{/each}
|
||||
{#if gameList.length > 0}
|
||||
{#each gameList as game}
|
||||
<option value={game.displayName}>{game.displayName}</option>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each gameOrder.length > 0 ? gameOrder : Object.keys(gameDexes) as game}
|
||||
<option value={game}>{game}</option>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if pokedex.gameScope}
|
||||
<div class="form-control w-full">
|
||||
<fieldset class="w-full">
|
||||
<legend class="label">
|
||||
<span class="label-text">Dex Scope</span>
|
||||
<span class="label-text-alt text-error">{!hasDexScope ? 'Select at least one dex' : ''}</span>
|
||||
</legend>
|
||||
{#if availableDexes.length === 0}
|
||||
<p class="text-sm text-error">No dexes found for this game.</p>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each availableDexes as dex}
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
value={dex.id}
|
||||
bind:group={pokedex.dexScopes}
|
||||
/>
|
||||
<span class="label-text">{dex.displayName}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</fieldset>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn btn-ghost" type="button" on:click={onCancel}>Cancel</button>
|
||||
<button class="btn btn-primary" type="button" on:click={handleSubmit} disabled={!canSubmit}>
|
||||
|
||||
@@ -31,11 +31,12 @@
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
class="modal-backdrop bg-black/50"
|
||||
aria-label="Close modal"
|
||||
on:click={handleBackdropClick}
|
||||
on:keydown={() => {}}
|
||||
></div>
|
||||
></button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -46,6 +47,8 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-box-custom {
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-4 relative z-20">
|
||||
<h2 class="text-xl font-bold">Box {boxNumber}</h2>
|
||||
<div class="relative" on:click|stopPropagation>
|
||||
<div class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline relative z-[210]"
|
||||
@@ -376,12 +376,11 @@
|
||||
<ul
|
||||
id={bulkMenuId}
|
||||
class="menu bg-base-100 rounded-box absolute right-0 mt-2 z-[220] w-56 p-2 shadow border border-base-300"
|
||||
on:click|stopPropagation
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
markBoxAsNotCaught(boxNumber);
|
||||
openBulkMenuForBox = null;
|
||||
}}
|
||||
@@ -392,7 +391,7 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
markBoxAsCaught(boxNumber);
|
||||
openBulkMenuForBox = null;
|
||||
}}
|
||||
@@ -403,7 +402,7 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
markBoxAsNeedsToEvolve(boxNumber);
|
||||
openBulkMenuForBox = null;
|
||||
}}
|
||||
@@ -414,7 +413,7 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
markBoxAsInHome(boxNumber);
|
||||
openBulkMenuForBox = null;
|
||||
}}
|
||||
@@ -425,7 +424,7 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
markBoxAsNotInHome(boxNumber);
|
||||
openBulkMenuForBox = null;
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user