diff --git a/.prettierignore b/.prettierignore index cc41cea..0eccc4e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,11 @@ pnpm-lock.yaml package-lock.json yarn.lock + +# Generated data exports. Nobody hand-edits these, and reformatting them buries real data +# changes under tens of thousands of lines of churn. +src/lib/helpers/pokeapi-pokemon.json +static/sprites/pokemon.json + +# Machine-local editor and tool settings. +**/*.local.json diff --git a/postcss.config.js b/postcss.config.js index 2e7af2b..0f77216 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/pwa.mjs b/pwa.mjs index 0f916fd..83f5869 100644 --- a/pwa.mjs +++ b/pwa.mjs @@ -1,3 +1,3 @@ -import process from 'node:process' +import process from 'node:process'; -export const generateSW = process.env.GENERATE_SW === 'true' +export const generateSW = process.env.GENERATE_SW === 'true'; diff --git a/scripts/csv-to-migration.js b/scripts/csv-to-migration.js index fcbb84a..ae8a99d 100644 --- a/scripts/csv-to-migration.js +++ b/scripts/csv-to-migration.js @@ -17,167 +17,181 @@ const __dirname = path.dirname(__filename); * Parse CSV file into array of objects */ function parseCSV(filePath) { - const content = fs.readFileSync(filePath, 'utf-8'); - const lines = content.split('\n').filter(line => line.trim()); - const headers = lines[0].split(','); + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').filter((line) => line.trim()); + const headers = lines[0].split(','); - return lines.slice(1).map(line => { - const values = parseCSVLine(line); - const obj = {}; - headers.forEach((header, i) => { - obj[header] = values[i] || null; - }); - return obj; - }); + return lines.slice(1).map((line) => { + const values = parseCSVLine(line); + const obj = {}; + headers.forEach((header, i) => { + obj[header] = values[i] || null; + }); + return obj; + }); } /** * Parse a single CSV line, handling quoted values */ function parseCSVLine(line) { - const values = []; - let current = ''; - let inQuotes = false; + const values = []; + let current = ''; + let inQuotes = false; - for (let i = 0; i < line.length; i++) { - const char = line[i]; + for (let i = 0; i < line.length; i++) { + const char = line[i]; - if (char === '"') { - inQuotes = !inQuotes; - } else if (char === ',' && !inQuotes) { - values.push(current); - current = ''; - } else { - current += char; - } - } - values.push(current); + if (char === '"') { + inQuotes = !inQuotes; + } else if (char === ',' && !inQuotes) { + values.push(current); + current = ''; + } else { + current += char; + } + } + values.push(current); - return values; + return values; } /** * Generate migration SQL from CSV data */ function generateMigration(region) { - console.log(`\nGenerating migration for ${region}...`); + console.log(`\nGenerating migration for ${region}...`); - // Determine generation number from region - const regionToGen = { - 'Kanto': 1, 'Johto': 2, 'Hoenn': 3, 'Sinnoh': 4, - 'Unova': 5, 'Kalos': 6, 'Alola': 7, 'Galar': 8, - 'Hisui': 8, 'Paldea': 9 - }; - const gen = regionToGen[region] || 1; + // Determine generation number from region + const regionToGen = { + Kanto: 1, + Johto: 2, + Hoenn: 3, + Sinnoh: 4, + Unova: 5, + Kalos: 6, + Alola: 7, + Galar: 8, + Hisui: 8, + Paldea: 9 + }; + const gen = regionToGen[region] || 1; - // 1. Load CSV files - const pokemonPath = path.join(__dirname, '..', 'data', 'pokemon', `gen${gen}-${region.toLowerCase()}.csv`); - const gamesPath = path.join(__dirname, '..', 'data', 'pokemon', 'games.csv'); + // 1. Load CSV files + const pokemonPath = path.join( + __dirname, + '..', + 'data', + 'pokemon', + `gen${gen}-${region.toLowerCase()}.csv` + ); + const gamesPath = path.join(__dirname, '..', 'data', 'pokemon', 'games.csv'); - if (!fs.existsSync(pokemonPath)) { - console.error(`Error: Pokemon CSV not found at ${pokemonPath}`); - process.exit(1); - } + if (!fs.existsSync(pokemonPath)) { + console.error(`Error: Pokemon CSV not found at ${pokemonPath}`); + process.exit(1); + } - const pokemon = parseCSV(pokemonPath); - const games = parseCSV(gamesPath); + const pokemon = parseCSV(pokemonPath); + const games = parseCSV(gamesPath); - // 2. Filter for this region - const regionGames = games.filter(g => g.region === region); + // 2. Filter for this region + const regionGames = games.filter((g) => g.region === region); - if (regionGames.length === 0) { - console.error(`Error: No games found for region ${region} in games.csv`); - process.exit(1); - } + if (regionGames.length === 0) { + console.error(`Error: No games found for region ${region} in games.csv`); + process.exit(1); + } - // 3. Generate SQL - let sql = `-- Seed ${region} region Pokémon (Gen ${gen})\n`; - sql += `-- Auto-generated from CSV files\n\n`; + // 3. Generate SQL + let sql = `-- Seed ${region} region Pokémon (Gen ${gen})\n`; + sql += `-- Auto-generated from CSV files\n\n`; - // Region-game mappings - sql += `-- Insert ${region} region-game mappings\n`; - sql += `INSERT INTO region_game_mappings (region, game) VALUES\n`; - sql += regionGames.map(g => ` ('${region}', '${g.displayName}')`).join(',\n'); - sql += `\nON CONFLICT (region, game) DO NOTHING;\n\n`; + // Region-game mappings + sql += `-- Insert ${region} region-game mappings\n`; + sql += `INSERT INTO region_game_mappings (region, game) VALUES\n`; + sql += regionGames.map((g) => ` ('${region}', '${g.displayName}')`).join(',\n'); + sql += `\nON CONFLICT (region, game) DO NOTHING;\n\n`; - // Pokemon entries (without regional dex number) - sql += `-- Insert ${region} Pokémon entries\n`; - sql += `INSERT INTO pokedex_entries (\n`; - sql += ` "pokedexNumber",\n`; - sql += ` pokemon,\n`; - sql += ` form,\n`; - sql += ` "canGigantamax",\n`; - sql += ` "regionToCatchIn",\n`; - sql += ` "gamesToCatchIn"\n`; - sql += `) VALUES\n`; + // Pokemon entries (without regional dex number) + sql += `-- Insert ${region} Pokémon entries\n`; + sql += `INSERT INTO pokedex_entries (\n`; + sql += ` "pokedexNumber",\n`; + sql += ` pokemon,\n`; + sql += ` form,\n`; + sql += ` "canGigantamax",\n`; + sql += ` "regionToCatchIn",\n`; + sql += ` "gamesToCatchIn"\n`; + sql += `) VALUES\n`; - const rows = pokemon.map(p => { - const form = p.form ? `'${p.form}'` : 'NULL'; - // Use regionalDexGames for the database (regional dex availability) - // originGames column is for future origin dex feature - const gamesField = p.regionalDexGames || p.games; // Fallback to old 'games' column for compatibility - const gamesList = gamesField.split('|'); - const gamesArray = `ARRAY[${gamesList.map(g => `'${g}'`).join(', ')}]`; + const rows = pokemon.map((p) => { + const form = p.form ? `'${p.form}'` : 'NULL'; + // Use regionalDexGames for the database (regional dex availability) + // originGames column is for future origin dex feature + const gamesField = p.regionalDexGames || p.games; // Fallback to old 'games' column for compatibility + const gamesList = gamesField.split('|'); + const gamesArray = `ARRAY[${gamesList.map((g) => `'${g}'`).join(', ')}]`; - return `(${p.pokedexNumber}, '${p.name}', ${form}, false, '${region}', ${gamesArray})`; - }); + return `(${p.pokedexNumber}, '${p.name}', ${form}, false, '${region}', ${gamesArray})`; + }); - sql += rows.join(',\n'); - sql += '\nON CONFLICT ON CONSTRAINT unique_pokemon_form DO NOTHING;\n\n'; + sql += rows.join(',\n'); + sql += '\nON CONFLICT ON CONSTRAINT unique_pokemon_form DO NOTHING;\n\n'; - // Regional dex numbers (separate table) - sql += `-- Insert ${region} regional dex numbers\n`; + // Regional dex numbers (separate table) + sql += `-- Insert ${region} regional dex numbers\n`; - const dexRows = pokemon - .filter(p => p.regionalNumber) // Only entries with regional dex numbers - .map(p => { - const formCondition = p.form - ? `form = '${p.form}'` - : `form IS NULL`; + const dexRows = pokemon + .filter((p) => p.regionalNumber) // Only entries with regional dex numbers + .map((p) => { + const formCondition = p.form ? `form = '${p.form}'` : `form IS NULL`; - return ` ((SELECT id FROM pokedex_entries WHERE "pokedexNumber" = ${p.pokedexNumber} AND ${formCondition}), '${region}', ${p.regionalNumber})`; - }); + return ` ((SELECT id FROM pokedex_entries WHERE "pokedexNumber" = ${p.pokedexNumber} AND ${formCondition}), '${region}', ${p.regionalNumber})`; + }); - if (dexRows.length > 0) { - sql += `INSERT INTO regional_dex_numbers (\n`; - sql += ` pokedex_entry_id,\n`; - sql += ` region,\n`; - sql += ` dex_number\n`; - sql += `) VALUES\n`; - sql += dexRows.join(',\n'); - sql += ';\n\n'; - } else { - sql += '-- No regional dex numbers for this region\n\n'; - } + if (dexRows.length > 0) { + sql += `INSERT INTO regional_dex_numbers (\n`; + sql += ` pokedex_entry_id,\n`; + sql += ` region,\n`; + sql += ` dex_number\n`; + sql += `) VALUES\n`; + sql += dexRows.join(',\n'); + sql += ';\n\n'; + } else { + sql += '-- No regional dex numbers for this region\n\n'; + } - // Add metadata - sql += `-- Add metadata\n`; - sql += `INSERT INTO metadata (key, value) VALUES\n`; - sql += ` ('${region.toLowerCase()}_seeded', 'true'),\n`; - sql += ` ('${region.toLowerCase()}_seed_date', NOW()::TEXT),\n`; - sql += ` ('${region.toLowerCase()}_pokemon_count', '${pokemon.filter(p => !p.form).length}');\n`; + // Add metadata + sql += `-- Add metadata\n`; + sql += `INSERT INTO metadata (key, value) VALUES\n`; + sql += ` ('${region.toLowerCase()}_seeded', 'true'),\n`; + sql += ` ('${region.toLowerCase()}_seed_date', NOW()::TEXT),\n`; + sql += ` ('${region.toLowerCase()}_pokemon_count', '${pokemon.filter((p) => !p.form).length}');\n`; - // 4. Write file - const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14); - const filename = `${timestamp}_seed_${region.toLowerCase()}.sql`; - const outputPath = path.join(__dirname, '..', 'supabase', 'migrations', filename); + // 4. Write file + const timestamp = new Date() + .toISOString() + .replace(/[-:T.]/g, '') + .slice(0, 14); + const filename = `${timestamp}_seed_${region.toLowerCase()}.sql`; + const outputPath = path.join(__dirname, '..', 'supabase', 'migrations', filename); - fs.writeFileSync(outputPath, sql); + fs.writeFileSync(outputPath, sql); - console.log(`✓ Generated ${filename}`); - console.log(` - ${pokemon.length} Pokemon entries`); - console.log(` - ${dexRows.length} regional dex numbers`); - console.log(` - ${regionGames.length} games\n`); + console.log(`✓ Generated ${filename}`); + console.log(` - ${pokemon.length} Pokemon entries`); + console.log(` - ${dexRows.length} regional dex numbers`); + console.log(` - ${regionGames.length} games\n`); - return filename; + return filename; } // Run const region = process.argv[2]; if (!region) { - console.error('Usage: node csv-to-migration.js '); - console.error('Example: node csv-to-migration.js Kanto'); - process.exit(1); + console.error('Usage: node csv-to-migration.js '); + console.error('Example: node csv-to-migration.js Kanto'); + process.exit(1); } generateMigration(region); diff --git a/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte b/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte index bb9c07a..ffc43d1 100644 --- a/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte +++ b/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte @@ -56,7 +56,6 @@ } updateCatchRecord('toggle'); } -
0); - $: canSubmit = - pokedex.name && pokedex.name.trim() !== '' && hasAtLeastOneType && hasDexScope; + $: 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; @@ -162,16 +160,14 @@ {#if loadingDexes} + {:else if gameList.length > 0} + {#each gameList as game} + + {/each} {:else} - {#if gameList.length > 0} - {#each gameList as game} - - {/each} - {:else} - {#each gameOrder.length > 0 ? gameOrder : Object.keys(gameDexes) as game} - - {/each} - {/if} + {#each gameOrder.length > 0 ? gameOrder : Object.keys(gameDexes) as game} + + {/each} {/if}
@@ -181,7 +177,9 @@
Dex Scope - {!hasDexScope ? 'Select at least one dex' : ''} + {!hasDexScope ? 'Select at least one dex' : ''} {#if availableDexes.length === 0}

No dexes found for this game.

diff --git a/src/lib/repositories/PokedexEntryRepository.ts b/src/lib/repositories/PokedexEntryRepository.ts index 56d9155..1f35efc 100644 --- a/src/lib/repositories/PokedexEntryRepository.ts +++ b/src/lib/repositories/PokedexEntryRepository.ts @@ -8,9 +8,7 @@ import type { SupabaseClient } from '@supabase/supabase-js'; class PokedexEntryRepository { constructor(private supabase: SupabaseClient) {} - private parseCatchInformation( - values: string[] | null - ): Array { + private parseCatchInformation(values: string[] | null): Array { if (!values) return []; return values.map((value) => { const trimmed = value.trim(); diff --git a/src/lib/services/PokedexDexScopeService.ts b/src/lib/services/PokedexDexScopeService.ts index cfb7116..5c0345d 100644 --- a/src/lib/services/PokedexDexScopeService.ts +++ b/src/lib/services/PokedexDexScopeService.ts @@ -84,9 +84,7 @@ export async function setPokedexDexScopes( } } -export async function listGameDexes( - supabase: SupabaseClient -): Promise<{ +export async function listGameDexes(supabase: SupabaseClient): Promise<{ gameDexes: Record; gameOrder: string[]; games: { displayName: string; releaseYear: number }[]; diff --git a/src/lib/utils/regionalDexMapping.ts b/src/lib/utils/regionalDexMapping.ts index cf2ba31..e068e61 100644 --- a/src/lib/utils/regionalDexMapping.ts +++ b/src/lib/utils/regionalDexMapping.ts @@ -24,62 +24,62 @@ type RegionalDexKey = const gameToRegionalDexMap: Record = { // Kanto region - 'Red': 'kanto', - 'Blue': 'kanto', - 'Yellow': 'kanto', - 'FireRed': 'kanto', - 'LeafGreen': 'kanto', + Red: 'kanto', + Blue: 'kanto', + Yellow: 'kanto', + FireRed: 'kanto', + LeafGreen: 'kanto', 'LG: Pikachu': 'kanto', 'LG: Eevee': 'kanto', // Johto region - 'Gold': 'johto', - 'Silver': 'johto', - 'Crystal': 'johto', - 'HeartGold': 'johto', - 'SoulSilver': 'johto', + Gold: 'johto', + Silver: 'johto', + Crystal: 'johto', + HeartGold: 'johto', + SoulSilver: 'johto', // Hoenn region - 'Ruby': 'hoenn', - 'Sapphire': 'hoenn', - 'Emerald': 'hoenn', - 'OmegaRuby': 'hoenn', - 'AlphaSapphire': 'hoenn', + Ruby: 'hoenn', + Sapphire: 'hoenn', + Emerald: 'hoenn', + OmegaRuby: 'hoenn', + AlphaSapphire: 'hoenn', // Sinnoh region - 'Diamond': 'sinnoh', - 'Pearl': 'sinnoh', - 'Platinum': 'sinnoh', - 'BrilliantDiamond': 'sinnoh', - 'ShiningPearl': 'sinnoh', + Diamond: 'sinnoh', + Pearl: 'sinnoh', + Platinum: 'sinnoh', + BrilliantDiamond: 'sinnoh', + ShiningPearl: 'sinnoh', // Unova region - 'Black': 'unova_bw', - 'White': 'unova_bw', - 'Black2': 'unova_b2w2', - 'White2': 'unova_b2w2', + Black: 'unova_bw', + White: 'unova_bw', + Black2: 'unova_b2w2', + White2: 'unova_b2w2', // Kalos region - Note: All XY use all three sub-dexes // We default to Central for simplicity - 'X': 'kalos_central', - 'Y': 'kalos_central', + X: 'kalos_central', + Y: 'kalos_central', // Alola region - 'Sun': 'alola_sm', - 'Moon': 'alola_sm', - 'UltraSun': 'alola_usum', - 'UltraMoon': 'alola_usum', + Sun: 'alola_sm', + Moon: 'alola_sm', + UltraSun: 'alola_usum', + UltraMoon: 'alola_usum', // Galar region - 'Sword': 'galar', - 'Shield': 'galar', + Sword: 'galar', + Shield: 'galar', // Hisui region - 'LegendsArceus': 'hisui', + LegendsArceus: 'hisui', // Paldea region - 'Scarlet': 'paldea', - 'Violet': 'paldea' + Scarlet: 'paldea', + Violet: 'paldea' }; /** @@ -119,22 +119,22 @@ export function getRegionalDexFieldName(gameName: string): string | undefined { // Map regional key to actual PokedexEntry field name (camelCase) const fieldMap: Record = { - 'kanto': 'kantoDexNumber', - 'johto': 'johtoDexNumber', - 'hoenn': 'hoennDexNumber', - 'sinnoh': 'sinnohDexNumber', - 'unova_bw': 'unovaBwDexNumber', - 'unova_b2w2': 'unovaB2w2DexNumber', - 'kalos_central': 'kalosCentralDexNumber', - 'kalos_coastal': 'kalosCoastalDexNumber', - 'kalos_mountain': 'kalosMountainDexNumber', - 'alola_sm': 'alolaSmDexNumber', - 'alola_usum': 'alolaUsumDexNumber', - 'galar': 'galarDexNumber', - 'galar_isle_of_armor': 'galarIsleOfArmorDexNumber', - 'galar_crown_tundra': 'galarCrownTundraDexNumber', - 'hisui': 'hisuiDexNumber', - 'paldea': 'paldeaDexNumber' + kanto: 'kantoDexNumber', + johto: 'johtoDexNumber', + hoenn: 'hoennDexNumber', + sinnoh: 'sinnohDexNumber', + unova_bw: 'unovaBwDexNumber', + unova_b2w2: 'unovaB2w2DexNumber', + kalos_central: 'kalosCentralDexNumber', + kalos_coastal: 'kalosCoastalDexNumber', + kalos_mountain: 'kalosMountainDexNumber', + alola_sm: 'alolaSmDexNumber', + alola_usum: 'alolaUsumDexNumber', + galar: 'galarDexNumber', + galar_isle_of_armor: 'galarIsleOfArmorDexNumber', + galar_crown_tundra: 'galarCrownTundraDexNumber', + hisui: 'hisuiDexNumber', + paldea: 'paldeaDexNumber' }; return fieldMap[regionalKey]; diff --git a/src/routes/api/export-integrations/[provider]/+server.ts b/src/routes/api/export-integrations/[provider]/+server.ts index d384c4f..2a584f3 100644 --- a/src/routes/api/export-integrations/[provider]/+server.ts +++ b/src/routes/api/export-integrations/[provider]/+server.ts @@ -47,7 +47,9 @@ export const PUT = async (event: RequestEvent) => { .eq('userId', userId) .is('pokedexId', null) .eq('provider', provider) - .select('id, provider, enabled, fileName, folderId, path, metadata, lastExportedAt, lastError') + .select( + 'id, provider, enabled, fileName, folderId, path, metadata, lastExportedAt, lastError' + ) .maybeSingle(); if (error) { diff --git a/src/routes/api/pokedexes/[id]/catch-records/+server.ts b/src/routes/api/pokedexes/[id]/catch-records/+server.ts index 72179fb..01ba831 100644 --- a/src/routes/api/pokedexes/[id]/catch-records/+server.ts +++ b/src/routes/api/pokedexes/[id]/catch-records/+server.ts @@ -31,9 +31,7 @@ export const GET = async (event: RequestEvent) => { const repo = new CatchRecordRepository(event.locals.supabase, userId, pokedexId); const catchData = await repo.findAll(); - const sortedData = catchData.sort( - (a, b) => Number(a.pokemonId) - Number(b.pokemonId) - ); + const sortedData = catchData.sort((a, b) => Number(a.pokemonId) - Number(b.pokemonId)); return json(sortedData); } catch (err) { console.error(err); @@ -159,10 +157,7 @@ export const POST = async (event: RequestEvent) => { try { await exportPokedexIfConfigured(event.locals.supabase, userId, pokedexId); } catch (exportError) { - console.error( - 'Failed to export pokedex after per-record catch updates:', - exportError - ); + console.error('Failed to export pokedex after per-record catch updates:', exportError); } return json(insertedRecords); } diff --git a/src/routes/my-pokedexes/+page.svelte b/src/routes/my-pokedexes/+page.svelte index 6b624bb..4de9b3d 100644 --- a/src/routes/my-pokedexes/+page.svelte +++ b/src/routes/my-pokedexes/+page.svelte @@ -204,11 +204,7 @@ - {/if}