diff --git a/src/views/ItemDetail.js b/src/views/ItemDetail.js index 36694e7..adf7242 100644 --- a/src/views/ItemDetail.js +++ b/src/views/ItemDetail.js @@ -1,9 +1,13 @@ import { el, clear } from '../lib/dom.js'; import { getItem } from '../data/api.js'; +import { loadSnapshot } from '../data/snapshot.js'; import { prettify } from '../data/pokedex-resolver.js'; +import { settings } from '../store/settings.js'; +import { Sprite } from '../components/Sprite.js'; const ITEM_SPRITE = (name) => `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/${name}.png`; +const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop()); const english = (entries, key) => { const hit = (entries || []).find((e) => e.language.name === 'en'); @@ -18,6 +22,8 @@ export async function ItemDetail(id) { const view = el('section', { class: 'view lookup' }); view.append(el('div', { class: 'view--loading' }, 'Loading item…')); + const snap = await loadSnapshot(); + let d; try { d = await getItem(id); @@ -42,6 +48,56 @@ export async function ItemDetail(id) { const img = el('img', { class: 'lookup__item-img', alt: d.name, src: ITEM_SPRITE(d.name) }); img.addEventListener('error', () => img.remove(), { once: true }); + // Pokémon that hold this item in the wild. held_by_pokemon carries a + // per-version breakdown, so for a specific game we can filter exactly. + const vg = settings.get().versionGroup; + const versions = new Set(snap.versionGroupByKey.get(vg)?.versions || []); + const holders = (d.held_by_pokemon || []) + .filter( + (h) => + vg === 'all' || + !versions.size || + (h.version_details || []).some((vd) => versions.has(vd.version.name)), + ) + .map((h) => snap.speciesById.get(idFromUrl(h.pokemon.url))) + .filter(Boolean) + .sort((a, b) => a.id - b.id); + + const style = settings.get().spriteStyle; + const HOLDER_CAP = 400; + const holdersGrid = el('div', { class: 'ab-mons' }); + function paintHolders(q) { + const qq = (q || '').trim().toLowerCase().replace(/\s+/g, '-'); + const shown = qq ? holders.filter((sp) => sp.name.includes(qq)) : holders; + clear(holdersGrid); + if (!shown.length) { + holdersGrid.append(el('p', { class: 'detail__muted' }, 'No matches.')); + return; + } + for (const sp of shown.slice(0, HOLDER_CAP)) { + holdersGrid.append( + el( + 'a', + { class: 'ab-mon', href: `#/pokemon/${sp.id}` }, + Sprite(sp.id, { style, alt: sp.name, size: 48 }), + el('span', {}, sp.name.replace(/-/g, ' ')), + ), + ); + } + if (shown.length > HOLDER_CAP) { + holdersGrid.append( + el('p', { class: 'detail__muted' }, `+${shown.length - HOLDER_CAP} more — filter to narrow.`), + ); + } + } + paintHolders(''); + const holderFilter = el('input', { + type: 'search', + class: 'search__input learners__filter', + placeholder: 'Filter Pokémon…', + oninput: (e) => paintHolders(e.target.value), + }); + clear(view).append( el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, '‹ Lookup')), el( @@ -63,9 +119,22 @@ export async function ItemDetail(id) { d.attributes?.length ? fact('Attributes', d.attributes.map((a) => prettify(a.name)).join(', ')) : null, - fact('Held by', `${d.held_by_pokemon.length} Pokémon`), + fact('Held by', `${holders.length} Pokémon`), ), el('section', { class: 'detail__section' }, el('h2', {}, 'Effect'), el('p', {}, effect)), + holders.length + ? el( + 'section', + { class: 'detail__section' }, + el( + 'h2', + {}, + `Held in the wild by${vg === 'all' ? '' : ` in ${prettify(vg)}`} (${holders.length})`, + ), + holders.length > 24 ? holderFilter : null, + holdersGrid, + ) + : null, ); return view; } diff --git a/src/views/PokemonDetail.js b/src/views/PokemonDetail.js index 9592083..1db732a 100644 --- a/src/views/PokemonDetail.js +++ b/src/views/PokemonDetail.js @@ -356,7 +356,20 @@ export async function PokemonDetail(nationalId) { const evYield = p.stats.filter((s) => s.effort > 0).map((s) => `${s.effort} ${STAT_LABEL[s.stat.name] || s.stat.name}`).join(', ') || '—'; - const heldItems = p.held_items?.map((h) => prettify(h.item.name)).join(', ') || '—'; + const heldItems = p.held_items?.length + ? el( + 'span', + {}, + ...p.held_items.flatMap((h, i) => { + const link = el( + 'a', + { class: 'link', href: `#/item/${idFromUrl(h.item.url)}` }, + prettify(h.item.name), + ); + return i === 0 ? [link] : [', ', link]; + }), + ) + : '—'; return el( 'div', {},