- Search becomes a tabbed lookup: Pokémon (offline snapshot) / Moves / Items. Moves + Items lazy-load a name index from PokéAPI (SW-cached) and filter client-side; short move result sets get type/class/power chips - MoveDetail (#/move/:id): type-themed header, power/accuracy/PP/priority, effect text with %-substitution, target, ailment/crit/drain/stat-change summary, learned-by count - ItemDetail (#/item/:id): item sprite, category, cost, fling power, attributes, held-by count, effect text - Loose name matching (hyphens vs spaces); run-token guards stale async renders when the query or tab changes mid-fetch Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.8 KiB
JavaScript
50 lines
1.8 KiB
JavaScript
/**
|
|
* Lazy detail fetches. Only detail pages call these; the list, search and
|
|
* game switching all run off the bundled snapshot.
|
|
*
|
|
* In-flight requests are de-duplicated in memory. Durable caching (across
|
|
* reloads and offline) is the service worker's job — see src/sw.js, which
|
|
* runs stale-while-revalidate over this origin.
|
|
*/
|
|
const BASE = 'https://pokeapi.co/api/v2';
|
|
const inflight = new Map();
|
|
|
|
export function getJSON(path) {
|
|
if (inflight.has(path)) return inflight.get(path);
|
|
const promise = fetch(`${BASE}/${path}`)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`${path} → ${res.status}`);
|
|
return res.json();
|
|
})
|
|
.finally(() => inflight.delete(path));
|
|
inflight.set(path, promise);
|
|
return promise;
|
|
}
|
|
|
|
export const getPokemon = (idOrName) => getJSON(`pokemon/${idOrName}`);
|
|
export const getSpecies = (idOrName) => getJSON(`pokemon-species/${idOrName}`);
|
|
export const getEvolutionChain = (id) => getJSON(`evolution-chain/${id}`);
|
|
export const getMove = (idOrName) => getJSON(`move/${idOrName}`);
|
|
export const getItem = (idOrName) => getJSON(`item/${idOrName}`);
|
|
export const getEncounters = (id) => getJSON(`pokemon/${id}/encounters`);
|
|
|
|
// Full name indexes for the lookup tabs — one request each, then the
|
|
// service worker keeps them (stale-while-revalidate).
|
|
export const getMoveIndex = () => getJSON('move?limit=2000');
|
|
export const getItemIndex = () => getJSON('item?limit=3000');
|
|
|
|
/** Run async `fn` over `items` with a bounded number of parallel workers. */
|
|
export async function mapLimit(items, limit, fn) {
|
|
const out = new Array(items.length);
|
|
let i = 0;
|
|
await Promise.all(
|
|
Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
while (i < items.length) {
|
|
const idx = i++;
|
|
out[idx] = await fn(items[idx], idx);
|
|
}
|
|
}),
|
|
);
|
|
return out;
|
|
}
|