data(*): Fix remaining sprites
@@ -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.
|
||||
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
|
||||
|
||||
To create a production version:
|
||||
|
||||
@@ -1,99 +1,75 @@
|
||||
<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';
|
||||
|
||||
export let pokemonName: string;
|
||||
export let pokedexNumber: string | number;
|
||||
export let form: string | undefined;
|
||||
export let spriteKey: string | undefined;
|
||||
export let shiny: boolean | undefined = false;
|
||||
|
||||
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 =
|
||||
PUBLIC_USE_LOCAL_POKEMON_SPRITE_FOLDER === 'true'
|
||||
? '/sprites/home'
|
||||
: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home';
|
||||
|
||||
if (form === 'Female') {
|
||||
rootFolder += '/female';
|
||||
}
|
||||
|
||||
if (shiny) {
|
||||
rootFolder += '/shiny';
|
||||
}
|
||||
|
||||
// Remove leading zeros
|
||||
const strippedPokedexNumber = pokedexNumber.toString().replace(/^0+/, '');
|
||||
|
||||
// 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;
|
||||
if (isFemaleForm(form)) {
|
||||
rootFolder += '/female';
|
||||
}
|
||||
|
||||
imagePath = `${rootFolder}/${pokeApiId}.png`;
|
||||
blah = `${pokeApiId}.png`;
|
||||
// blah = `${strippedPokedexNumber}${form?.length && form !== 'Female' ? '-' + form : ''}.png`;
|
||||
const resolvedSpriteKey = spriteKey?.trim() || buildFallbackKey();
|
||||
if (!spriteKey?.trim()) {
|
||||
console.warn('Missing sprite key for pokemon entry', {
|
||||
pokemonName,
|
||||
pokedexNumber,
|
||||
form
|
||||
});
|
||||
}
|
||||
|
||||
return `${rootFolder}/${resolvedSpriteKey}.png`;
|
||||
}
|
||||
|
||||
onMount(setImagePath);
|
||||
$: imagePath = computeImagePath();
|
||||
</script>
|
||||
|
||||
{#if imagePath}
|
||||
<!-- {blah} -->
|
||||
<img src={imagePath} alt="sprite" />
|
||||
{:else}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
pokemonName={pokedexEntry.pokemon}
|
||||
pokedexNumber={pokedexEntry.pokedexNumber}
|
||||
form={pokedexEntry.form}
|
||||
spriteKey={pokedexEntry.spriteKey}
|
||||
shiny={showShiny}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
pokemonName={pokedexEntry.pokemon}
|
||||
pokedexNumber={pokedexEntry.pokedexNumber}
|
||||
form={pokedexEntry.form}
|
||||
spriteKey={pokedexEntry.spriteKey}
|
||||
shiny={showShiny}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface PokedexEntry {
|
||||
pokedexNumber: number;
|
||||
pokemon: string;
|
||||
form: string;
|
||||
spriteKey: string;
|
||||
canGigantamax: boolean;
|
||||
regionToCatchIn: string;
|
||||
gamesToCatchIn: string[];
|
||||
@@ -25,6 +26,7 @@ export interface PokedexEntryDB {
|
||||
pokedexNumber: number;
|
||||
pokemon: string;
|
||||
form: string | null;
|
||||
spriteKey: string | null;
|
||||
canGigantamax: boolean;
|
||||
regionToCatchIn: string | null;
|
||||
gamesToCatchIn: string[] | null;
|
||||
|
||||
@@ -19,6 +19,7 @@ class CombinedDataRepository {
|
||||
pokedexNumber: entry.pokedexNumber,
|
||||
pokemon: entry.pokemon,
|
||||
form: entry.form || '',
|
||||
spriteKey: entry.spriteKey || '',
|
||||
canGigantamax: entry.canGigantamax,
|
||||
regionToCatchIn: entry.regionToCatchIn || '',
|
||||
gamesToCatchIn: entry.gamesToCatchIn || [],
|
||||
|
||||
@@ -47,6 +47,7 @@ class PokedexEntryRepository {
|
||||
pokedexNumber: entry.pokedexNumber,
|
||||
pokemon: entry.pokemon,
|
||||
form: entry.form || '',
|
||||
spriteKey: entry.spriteKey || '',
|
||||
canGigantamax: entry.canGigantamax,
|
||||
regionToCatchIn: entry.regionToCatchIn || '',
|
||||
gamesToCatchIn: entry.gamesToCatchIn || [],
|
||||
@@ -83,6 +84,7 @@ class PokedexEntryRepository {
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
|
||||
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
|
||||
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.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn;
|
||||
if (data.gamesToCatchIn !== undefined) dbData.gamesToCatchIn = data.gamesToCatchIn;
|
||||
@@ -110,6 +112,7 @@ class PokedexEntryRepository {
|
||||
if (data.pokedexNumber !== undefined) dbData.pokedexNumber = data.pokedexNumber;
|
||||
if (data.pokemon !== undefined) dbData.pokemon = data.pokemon;
|
||||
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.regionToCatchIn !== undefined) dbData.regionToCatchIn = data.regionToCatchIn;
|
||||
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,
|
||||
pokemon TEXT NOT NULL,
|
||||
form TEXT,
|
||||
"spriteKey" TEXT,
|
||||
"originRegionId" BIGINT NOT NULL REFERENCES regions(id) ON DELETE RESTRICT,
|
||||
"canGigantamax" BOOLEAN DEFAULT FALSE,
|
||||
"regionToEvolveIn" TEXT,
|
||||
@@ -334,6 +335,7 @@ SELECT
|
||||
p."pokedexNumber",
|
||||
p.pokemon,
|
||||
p.form,
|
||||
p."spriteKey",
|
||||
p."canGigantamax",
|
||||
r.name AS "regionToCatchIn",
|
||||
r."releaseOrder" AS "regionReleaseOrder",
|
||||
|
||||