import { el, clear, onTeardown } from '../lib/dom.js'; import { loadSnapshot } from '../data/snapshot.js'; import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js'; import { settings } from '../store/settings.js'; import { selection, stats } from '../store/selection.js'; import { Card } from '../components/Card.js'; import { ProgressRing } from '../components/ProgressRing.js'; const FILTERS = [ { key: 'all', label: 'All', test: () => true }, { key: 'caught', label: 'Caught', test: (e) => e.caught }, { key: 'missing', label: 'Missing', test: (e) => !e.caught }, { key: 'favorite', label: 'Favorites', test: (e) => e.favorite }, ]; const SEARCH_ICON = ''; export async function DexGrid() { const view = el('section', { class: 'view dexgrid' }); const snap = await loadSnapshot(); const grid = el('div', { class: 'grid' }); let query = ''; let filterKey = 'all'; let rows = []; let ids = []; const ring = ProgressRing(); const title = el('h1', {}); const gamePill = el( 'a', { class: 'game-pill', href: '#/games' }, el('span', {}), el('span', { class: 'game-pill__chev', 'aria-hidden': 'true' }, '⌄'), ); const search = el('input', { class: 'field-search__input', type: 'search', placeholder: 'Filter this Pokédex…', oninput: (e) => { query = e.target.value.trim().toLowerCase(); paintGrid(); }, }); const filterBar = el('div', { class: 'feed-chips' }); const chipEls = FILTERS.map((f) => { const count = el('span', { class: 'feed-chip__count' }); const btn = el( 'button', { class: 'feed-chip', type: 'button', onclick: () => { filterKey = f.key; chipEls.forEach((c, i) => c.btn.classList.toggle('is-active', FILTERS[i].key === f.key)); paintGrid(); }, }, el('span', {}, f.label), count, ); filterBar.append(btn); return { btn, count }; }); chipEls[0].btn.classList.add('is-active'); view.append( el( 'header', { class: 'feed-head' }, el( 'div', { class: 'feed-head__top' }, el('div', { class: 'feed-head__id' }, title, gamePill), ring.node, ), el( 'div', { class: 'field-search' }, el('span', { class: 'field-search__icon', html: SEARCH_ICON, 'aria-hidden': 'true' }), search, ), filterBar, ), grid, ); function counts() { const p = selection.get().pokemon; let caught = 0; let favorite = 0; for (const id of ids) { const e = p[id]; if (!e) continue; if (e.caught) caught++; if (e.favorite) favorite++; } return { all: ids.length, caught, missing: ids.length - caught, favorite }; } function refreshMeta() { ring.update(stats(ids)); const c = counts(); chipEls.forEach(({ count }, i) => { count.textContent = c[FILTERS[i].key]; }); } function paintGrid() { const st = settings.get(); const pokemonState = selection.get().pokemon; // Gen 1–2 game-era sprites have opaque white backgrounds — frame them. const boxed = st.spriteStyle === 'game' && (snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9) <= 2; const filterTest = FILTERS.find((f) => f.key === filterKey).test; const frag = document.createDocumentFragment(); let shown = 0; for (const { species, number } of rows) { if ( query && !species.name.includes(query) && String(number) !== query && String(species.id) !== query ) { continue; } const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false }; if (!filterTest(e)) continue; const card = Card(species, number, { spriteStyle: st.spriteStyle, versionGroup: st.versionGroup, boxed, }); if (shown < 30) card.style.setProperty('--i', String(shown)); frag.append(card); shown++; } clear(grid); grid.append( shown === 0 ? el('p', { class: 'grid__empty' }, 'Nothing matches those filters.') : frag, ); } function rebuild() { const st = settings.get(); const dex = resolvePokedex(snap, st); rows = dexRows(snap, dex); ids = rows.map((r) => r.species.id); title.textContent = prettify(dex.name || dex.key); gamePill.firstChild.textContent = prettify(st.versionGroup); refreshMeta(); paintGrid(); } rebuild(); const offSettings = settings.subscribe(rebuild); const offSelection = selection.subscribe(() => { refreshMeta(); if (filterKey !== 'all') paintGrid(); }); onTeardown(view, () => { offSettings(); offSelection(); }); return view; }