Item detail: list holders; link held items on the Pokémon page
Two more spots where data was shown as a dead number/label: - ItemDetail "Held by N Pokémon" -> a clickable sprite grid, same as the move page. held_by_pokemon carries a per-version breakdown, so a specific game filters exactly (Leftovers in SV lists only what holds it there, not the all-games union). - PokemonDetail "Held items" in the Training facts were plain text; now each links to its item detail page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
86e9bc60bc
commit
9810eb0996
@ -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;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
{},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user