Dex grid: ability, egg group, min BST and fully-evolved filters
Extends the existing filter drawer (type/gen) with four more: - Ability — snapshot abilities already carry a reverse pokemon[] index, so this is a plain lookup, no new data needed. - Egg group — options derived from the species list's own eggGroups. - Min BST — a number field; hard filter, distinct from sorting by BST. - Fully evolved only — new species.evolvesFromId field (from pokemon-species' evolves_from_species, already fetched during the snapshot build, so no extra requests). "Fully evolved" is computed client-side as "no other species points here via evolvesFromId" — correctly handles branching lines (Eevee's evolutions all count as fully evolved; Eevee itself doesn't). All four persist via the ui store and count toward the filter badge, same as the existing filters. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
fecafe139c
commit
c9807127d6
@ -70,7 +70,10 @@ src/
|
||||
Working app with a type-themed UI:
|
||||
|
||||
- **Dex grid** — type-tinted cards (official artwork, spotlight, ghost number),
|
||||
per-dex progress, name/number filter, caught/favorite filters.
|
||||
per-dex progress, name/number filter, caught/favorite filters, plus a
|
||||
filter drawer: type, generation, ability, egg group, minimum BST, and
|
||||
"fully evolved only" (derived client-side from each species' evolves-from
|
||||
link — no extra fetches).
|
||||
- **Forms** — Megas, Gigantamax, regional forms and alternate formes in the
|
||||
snapshot; a pill switcher on the detail page rebuilds types / stats /
|
||||
matchups / abilities / learnset / artwork for the chosen form. Purely
|
||||
|
||||
@ -312,6 +312,11 @@ async function main() {
|
||||
color: sp.color?.name || null,
|
||||
eggGroups: sp.egg_groups.map((g) => g.name),
|
||||
genderRate: sp.gender_rate,
|
||||
// Species this evolves FROM (null for base forms) — lets the app
|
||||
// derive "fully evolved" client-side without fetching every
|
||||
// evolution chain: a species nothing points to via this field has no
|
||||
// further evolution.
|
||||
evolvesFromId: sp.evolves_from_species ? idFromUrl(sp.evolves_from_species.url) : null,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -15,6 +15,10 @@ export const ui = createStore('pdx.ui', {
|
||||
filter: 'all',
|
||||
filterType: '',
|
||||
filterGen: 0,
|
||||
filterAbility: 0,
|
||||
filterEggGroup: '',
|
||||
filterMinBst: 0,
|
||||
filterFullyEvolved: false,
|
||||
recent: [],
|
||||
toolsOpen: false,
|
||||
teamMode: 'coverage',
|
||||
|
||||
@ -1837,6 +1837,29 @@
|
||||
max-width: none;
|
||||
flex: 1;
|
||||
}
|
||||
.feed-minbst {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
padding: 7px 12px;
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 999px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.feed-minbst input {
|
||||
width: 4.5em;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
}
|
||||
.recent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -82,6 +82,10 @@ export async function DexGrid() {
|
||||
let filterKey = ui.get().filter || 'all';
|
||||
let filterType = ui.get().filterType || '';
|
||||
let filterGen = Number(ui.get().filterGen) || 0;
|
||||
let filterAbility = Number(ui.get().filterAbility) || 0;
|
||||
let filterEggGroup = ui.get().filterEggGroup || '';
|
||||
let filterMinBst = Number(ui.get().filterMinBst) || 0;
|
||||
let filterFullyEvolved = !!ui.get().filterFullyEvolved;
|
||||
let sortKey = ui.get().sort || 'dex';
|
||||
let sortDesc = !!ui.get().sortDesc;
|
||||
let rows = [];
|
||||
@ -210,6 +214,87 @@ export async function DexGrid() {
|
||||
'🎲',
|
||||
);
|
||||
|
||||
// "Fully evolved" = no other species lists this one as evolvesFromId —
|
||||
// derived client-side rather than fetching every evolution chain.
|
||||
const hasNextEvo = new Set(snap.species.map((s) => s.evolvesFromId).filter(Boolean));
|
||||
const isFullyEvolved = (sp) => !hasNextEvo.has(sp.id);
|
||||
|
||||
const eggGroupList = [...new Set(snap.species.flatMap((s) => s.eggGroups || []))].sort();
|
||||
const abilityList = snap.abilities.slice().sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const abilitySelect = el(
|
||||
'select',
|
||||
{
|
||||
class: 'feed-sort__select',
|
||||
onchange: (e) => {
|
||||
filterAbility = Number(e.target.value) || 0;
|
||||
ui.set({ filterAbility });
|
||||
paintGrid();
|
||||
resetScroll();
|
||||
syncToolsBadge();
|
||||
},
|
||||
},
|
||||
el('option', { value: '0' }, 'Any ability'),
|
||||
...abilityList.map((a) =>
|
||||
el('option', { value: String(a.id), selected: a.id === filterAbility }, prettify(a.name)),
|
||||
),
|
||||
);
|
||||
|
||||
const eggGroupSelect = el(
|
||||
'select',
|
||||
{
|
||||
class: 'feed-sort__select',
|
||||
onchange: (e) => {
|
||||
filterEggGroup = e.target.value;
|
||||
ui.set({ filterEggGroup });
|
||||
paintGrid();
|
||||
resetScroll();
|
||||
syncToolsBadge();
|
||||
},
|
||||
},
|
||||
el('option', { value: '' }, 'Any egg group'),
|
||||
...eggGroupList.map((g) => el('option', { value: g, selected: g === filterEggGroup }, prettify(g))),
|
||||
);
|
||||
|
||||
const minBstField = el(
|
||||
'label',
|
||||
{ class: 'feed-minbst' },
|
||||
el('span', {}, 'Min BST'),
|
||||
el('input', {
|
||||
type: 'number',
|
||||
min: '0',
|
||||
max: '1000',
|
||||
step: '10',
|
||||
placeholder: '0',
|
||||
value: filterMinBst || '',
|
||||
onchange: (e) => {
|
||||
filterMinBst = Math.max(0, Number(e.target.value) || 0);
|
||||
e.target.value = filterMinBst || '';
|
||||
ui.set({ filterMinBst });
|
||||
paintGrid();
|
||||
resetScroll();
|
||||
syncToolsBadge();
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const fullyEvolvedCheck = el(
|
||||
'label',
|
||||
{ class: 'lookup__check' },
|
||||
el('input', {
|
||||
type: 'checkbox',
|
||||
checked: filterFullyEvolved,
|
||||
onchange: (e) => {
|
||||
filterFullyEvolved = e.target.checked;
|
||||
ui.set({ filterFullyEvolved });
|
||||
paintGrid();
|
||||
resetScroll();
|
||||
syncToolsBadge();
|
||||
},
|
||||
}),
|
||||
'Fully evolved only',
|
||||
);
|
||||
|
||||
// Changing the list order/contents makes the old scroll offset meaningless.
|
||||
function resetScroll() {
|
||||
ui.set({ feedScroll: 0 });
|
||||
@ -263,6 +348,8 @@ export async function DexGrid() {
|
||||
filterBar,
|
||||
el('div', { class: 'feed-sort' }, el('span', { class: 'feed-sort__label' }, 'Sort'), sortSelect, dirBtn),
|
||||
el('div', { class: 'feed-sort feed-filters' }, typeSelect, genSelect, randomBtn),
|
||||
el('div', { class: 'feed-sort feed-filters' }, abilitySelect, eggGroupSelect, minBstField),
|
||||
el('div', { class: 'feed-sort feed-filters' }, fullyEvolvedCheck),
|
||||
);
|
||||
const toolsToggle = el(
|
||||
'button',
|
||||
@ -281,7 +368,13 @@ export async function DexGrid() {
|
||||
);
|
||||
function syncToolsBadge() {
|
||||
const n =
|
||||
(filterKey !== 'all' ? 1 : 0) + (filterType ? 1 : 0) + (filterGen ? 1 : 0);
|
||||
(filterKey !== 'all' ? 1 : 0) +
|
||||
(filterType ? 1 : 0) +
|
||||
(filterGen ? 1 : 0) +
|
||||
(filterAbility ? 1 : 0) +
|
||||
(filterEggGroup ? 1 : 0) +
|
||||
(filterMinBst ? 1 : 0) +
|
||||
(filterFullyEvolved ? 1 : 0);
|
||||
toolsToggle.dataset.active = n ? String(n) : '';
|
||||
}
|
||||
syncToolsBadge();
|
||||
@ -337,6 +430,7 @@ export async function DexGrid() {
|
||||
const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9;
|
||||
const boxed = st.spriteStyle === 'game' && gen <= 2;
|
||||
const filterTest = FILTERS.find((f) => f.key === filterKey).test;
|
||||
const abilityMon = filterAbility ? new Set(snap.abilityById.get(filterAbility)?.pokemon || []) : null;
|
||||
|
||||
let list = rows.filter(({ species, number }) => {
|
||||
if (
|
||||
@ -349,6 +443,10 @@ export async function DexGrid() {
|
||||
}
|
||||
if (filterGen && species.generation !== filterGen) return false;
|
||||
if (filterType && !typesForGen(species, gen).includes(filterType)) return false;
|
||||
if (abilityMon && !abilityMon.has(species.id)) return false;
|
||||
if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false;
|
||||
if (filterMinBst && (species.bst || 0) < filterMinBst) return false;
|
||||
if (filterFullyEvolved && !isFullyEvolved(species)) return false;
|
||||
const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false };
|
||||
return filterTest(e, species);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user