data(*): Fix remaining sprites

This commit is contained in:
Josh Creek
2026-01-18 13:14:18 +00:00
parent 71df0de3df
commit eba34158fe
18 changed files with 2843 additions and 2854 deletions
+4
View File
@@ -19,6 +19,10 @@ A web app to track completion of a living Pokédex.
N.B. All local emails are captured by MailPit when running Supabase in Docker. N.B. All local emails are captured by MailPit when running Supabase in Docker.
8. You can now use [the app](http://localhost:5173/). 8. You can now use [the app](http://localhost:5173/).
## Reference Data Updates
The seed data lives in `supabase/migrations/20260118001000_seed_reference_data.sql`. Update that migration directly when new data is added.
## Building ## Building
To create a production version: To create a production version:
+1391 -1391
View File
File diff suppressed because it is too large Load Diff
+46 -70
View File
@@ -1,99 +1,75 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte';
import pokeApiPokemon from '$lib/helpers/pokeapi-pokemon.json';
import { PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER } from '$env/static/public'; import { PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER } from '$env/static/public';
export let pokemonName: string; export let pokemonName: string;
export let pokedexNumber: string | number; export let pokedexNumber: string | number;
export let form: string | undefined; export let form: string | undefined;
export let spriteKey: string | undefined;
export let shiny: boolean | undefined = false; export let shiny: boolean | undefined = false;
let imagePath = null as string | null; let imagePath = null as string | null;
let blah = '';
function setImagePath() { function isFemaleForm(value?: string) {
return /^female\b/i.test((value ?? '').trim());
}
function buildFallbackKey() {
const strippedPokedexNumber = pokedexNumber.toString().replace(/^0+/, '') || '0';
if (!form) return strippedPokedexNumber;
let formValue = form.trim();
formValue = formValue.replace(/^female[-\s]*/i, '');
formValue = formValue.replace(/\s*\(.*?\)/g, '').replace(/\s*\[.*?\]/g, '').trim();
if (!formValue || formValue.toLowerCase() === 'male') return strippedPokedexNumber;
formValue = formValue
.toLowerCase()
.replace(/%/g, '')
.replace(/\balolan\b/g, 'alola')
.replace(/\bgalarian\b/g, 'galar')
.replace(/\bhisuian\b/g, 'hisui')
.replace(/\bpaldean\b/g, 'paldea')
.replace(/\bform(e)?$/, '')
.replace(/\bability$/, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
.replace(/2/g, 'two')
.replace(/3/g, 'three')
.replace(/4/g, 'four');
return formValue ? `${strippedPokedexNumber}-${formValue}` : strippedPokedexNumber;
}
function computeImagePath() {
let rootFolder = let rootFolder =
PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER === 'true' PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER === 'true'
? '/sprites/home' ? '/sprites/home'
: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home'; : 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home';
if (form === 'Female') {
rootFolder += '/female';
}
if (shiny) { if (shiny) {
rootFolder += '/shiny'; rootFolder += '/shiny';
} }
// Remove leading zeros if (isFemaleForm(form)) {
const strippedPokedexNumber = pokedexNumber.toString().replace(/^0+/, ''); rootFolder += '/female';
// Sanitise the pokemon name by making it all lowercase and replacing any spaces with hyphens and removing other characters
let sanitisedPokemonName = pokemonName.toLowerCase().replace(/[^a-z]/g, '');
/**
* Sprite Resolution Strategy:
* 1. For forms with PokeAPI entries (regional forms): Use PokeAPI ID (e.g., 10107.png)
* 2. For forms without PokeAPI entries (Unown, Burmy, etc.): Use {pokedexNumber}-{form} (e.g., 201-a.png)
* 3. For base forms: Use PokeAPI ID matching species_id
*/
let pokeApiId;
if (form && form.length > 0 && form !== 'Female') {
// Sanitise the form by making it all lowercase and replacing spaces with hyphens
let sanitisedForm = form
.toLowerCase()
.replaceAll(' ', '-')
.replaceAll('!', 'exclamation')
.replaceAll('?', 'question')
.replaceAll('2', 'two')
.replaceAll('3', 'three')
.replaceAll('4', 'four');
switch (sanitisedForm) {
case 'alolan':
sanitisedForm = 'alola';
break;
case 'galarian':
sanitisedForm = 'galar';
break;
case 'hisuian':
sanitisedForm = 'hisui';
break;
// case 'rainbow-ribbon':
// sanitisedForm = 'rainbow-swirl-ribbon-sweet';
// break;
default:
break;
}
// Try to find by PokeAPI identifier (e.g., "meowth-alola" -> 10107)
const pokeApiEntry = pokeApiPokemon.find(
(pokemon) => pokemon.identifier === sanitisedPokemonName + '-' + sanitisedForm
);
if (pokeApiEntry) {
// Pattern 1: Use PokeAPI ID (e.g., 10107.png for Meowth-Alola)
pokeApiId = pokeApiEntry.id;
} else {
// Pattern 2: Use {pokedexNumber}-{form} (e.g., 201-a.png for Unown-A)
pokeApiId = `${strippedPokedexNumber}-${sanitisedForm}`;
}
} else {
pokeApiId = pokeApiPokemon.find(
(pokemon) => pokemon.species_id.toString() === strippedPokedexNumber
)?.id;
} }
imagePath = `${rootFolder}/${pokeApiId}.png`; const resolvedSpriteKey = spriteKey?.trim() || buildFallbackKey();
blah = `${pokeApiId}.png`; if (!spriteKey?.trim()) {
// blah = `${strippedPokedexNumber}${form?.length && form !== 'Female' ? '-' + form : ''}.png`; console.warn('Missing sprite key for pokemon entry', {
pokemonName,
pokedexNumber,
form
});
}
return `${rootFolder}/${resolvedSpriteKey}.png`;
} }
onMount(setImagePath); $: imagePath = computeImagePath();
</script> </script>
{#if imagePath} {#if imagePath}
<!-- {blah} -->
<img src={imagePath} alt="sprite" /> <img src={imagePath} alt="sprite" />
{:else} {:else}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
@@ -68,6 +68,7 @@
pokemonName={pokedexEntry.pokemon} pokemonName={pokedexEntry.pokemon}
pokedexNumber={pokedexEntry.pokedexNumber} pokedexNumber={pokedexEntry.pokedexNumber}
form={pokedexEntry.form} form={pokedexEntry.form}
spriteKey={pokedexEntry.spriteKey}
shiny={showShiny} shiny={showShiny}
/> />
</div> </div>
@@ -530,6 +530,7 @@
pokemonName={pokedexEntry.pokemon} pokemonName={pokedexEntry.pokemon}
pokedexNumber={pokedexEntry.pokedexNumber} pokedexNumber={pokedexEntry.pokedexNumber}
form={pokedexEntry.form} form={pokedexEntry.form}
spriteKey={pokedexEntry.spriteKey}
shiny={showShiny} shiny={showShiny}
/> />
</div> </div>
+2
View File
@@ -10,6 +10,7 @@ export interface PokedexEntry {
pokedexNumber: number; pokedexNumber: number;
pokemon: string; pokemon: string;
form: string; form: string;
spriteKey: string;
canGigantamax: boolean; canGigantamax: boolean;
regionToCatchIn: string; regionToCatchIn: string;
gamesToCatchIn: string[]; gamesToCatchIn: string[];
@@ -25,6 +26,7 @@ export interface PokedexEntryDB {
pokedexNumber: number; pokedexNumber: number;
pokemon: string; pokemon: string;
form: string | null; form: string | null;
spriteKey: string | null;
canGigantamax: boolean; canGigantamax: boolean;
regionToCatchIn: string | null; regionToCatchIn: string | null;
gamesToCatchIn: string[] | null; gamesToCatchIn: string[] | null;
@@ -19,6 +19,7 @@ class CombinedDataRepository {
pokedexNumber: entry.pokedexNumber, pokedexNumber: entry.pokedexNumber,
pokemon: entry.pokemon, pokemon: entry.pokemon,
form: entry.form || '', form: entry.form || '',
spriteKey: entry.spriteKey || '',
canGigantamax: entry.canGigantamax, canGigantamax: entry.canGigantamax,
regionToCatchIn: entry.regionToCatchIn || '', regionToCatchIn: entry.regionToCatchIn || '',
gamesToCatchIn: entry.gamesToCatchIn || [], gamesToCatchIn: entry.gamesToCatchIn || [],
@@ -47,6 +47,7 @@ class PokedexEntryRepository {
pokedexNumber: entry.pokedexNumber, pokedexNumber: entry.pokedexNumber,
pokemon: entry.pokemon, pokemon: entry.pokemon,
form: entry.form || '', form: entry.form || '',
spriteKey: entry.spriteKey || '',
canGigantamax: entry.canGigantamax, canGigantamax: entry.canGigantamax,
regionToCatchIn: entry.regionToCatchIn || '', regionToCatchIn: entry.regionToCatchIn || '',
gamesToCatchIn: entry.gamesToCatchIn || [], gamesToCatchIn: entry.gamesToCatchIn || [],
@@ -83,6 +84,7 @@ class PokedexEntryRepository {
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber; if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon; if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
if (data.form !== undefined) dbData.form = data.form; if (data.form !== undefined) dbData.form = data.form;
if (data.spriteKey !== undefined) dbData.spriteKey = data.spriteKey;
if (data.canGigantamax !== undefined) dbData.canGigantamax = data.canGigantamax; if (data.canGigantamax !== undefined) dbData.canGigantamax = data.canGigantamax;
if (data.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn; if (data.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn;
if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn; if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn;
@@ -110,6 +112,7 @@ class PokedexEntryRepository {
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber; if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon; if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
if (data.form !== undefined) dbData.form = data.form; if (data.form !== undefined) dbData.form = data.form;
if (data.spriteKey !== undefined) dbData.spriteKey = data.spriteKey;
if (data.canGigantamax !== undefined) dbData.canGigantamax = data.canGigantamax; if (data.canGigantamax !== undefined) dbData.canGigantamax = data.canGigantamax;
if (data.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn; if (data.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn;
if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn; if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn;

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

@@ -57,6 +57,7 @@ CREATE TABLE pokemon (
"pokedexNumber" INTEGER NOT NULL, "pokedexNumber" INTEGER NOT NULL,
pokemon TEXT NOT NULL, pokemon TEXT NOT NULL,
form TEXT, form TEXT,
"spriteKey" TEXT,
"originRegionId" BIGINT NOT NULL REFERENCES regions(id) ON DELETE RESTRICT, "originRegionId" BIGINT NOT NULL REFERENCES regions(id) ON DELETE RESTRICT,
"canGigantamax" BOOLEAN DEFAULT FALSE, "canGigantamax" BOOLEAN DEFAULT FALSE,
"regionToEvolveIn" TEXT, "regionToEvolveIn" TEXT,
@@ -334,6 +335,7 @@ SELECT
p."pokedexNumber", p."pokedexNumber",
p.pokemon, p.pokemon,
p.form, p.form,
p."spriteKey",
p."canGigantamax", p."canGigantamax",
r.name AS "regionToCatchIn", r.name AS "regionToCatchIn",
r."releaseOrder" AS "regionReleaseOrder", r."releaseOrder" AS "regionReleaseOrder",
File diff suppressed because it is too large Load Diff