Browsable moves & items lists (offline)
- Snapshot now bakes a moves index (937: type, damage class, power,
accuracy, pp, priority, generation, machine ids per version group) and an
items index (2223: id, name, category — resolved via the 54 item
categories). ~806 KB / 117 KB gzip.
- Search Moves/Items tabs show the FULL list when the query is empty:
- Moves: filter by type / damage class / 'TMs only' (for the selected
game), sort by A–Z / power / accuracy / newest; TM numbers resolve
lazily via /machine. Rich rows (type + class + power) with no fetch.
- Items: filter by category, sort A–Z / dex order; sprite + category tag.
- Result count shown; capped at 400 with a 'refine to see more' note.
- Both tabs are now fully offline (dropped the online name-index calls).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ddc84c2a48
commit
79a5efaf11
10
README.md
10
README.md
@ -80,9 +80,13 @@ Working app with a type-themed UI:
|
|||||||
applies Gen 1 / pre-Gen 6 rules.
|
applies Gen 1 / pre-Gen 6 rules.
|
||||||
- **Games** — overlay picker with stylised version-colour cover tiles,
|
- **Games** — overlay picker with stylised version-colour cover tiles,
|
||||||
sub-dex switch, and an "All games" (National, no gen limits) option.
|
sub-dex switch, and an "All games" (National, no gen limits) option.
|
||||||
- **Search** — tabbed lookup: Pokémon (offline, from the snapshot), Moves
|
- **Search** — tabbed lookup: Pokémon, Moves and Items, all browsable
|
||||||
and Items (lazy name index + on-demand detail, SW-cached), each with its
|
offline from the snapshot. Moves filter by type / damage class / "TMs in
|
||||||
own detail page. Query, scroll and tab persist.
|
this game" and sort by power / accuracy / recency; Items filter by
|
||||||
|
category. Each has its own detail page. Query, scroll, tab and filters
|
||||||
|
persist.
|
||||||
|
- **Team** — a lineup of up to 6 with a Coverage table (weaknesses, STAB
|
||||||
|
gaps) and a Compare table; toggleable in the nav from Settings.
|
||||||
- **Settings** — theme (System / Light / Dark / Black / Sepia) + accent
|
- **Settings** — theme (System / Light / Dark / Black / Sepia) + accent
|
||||||
colour, sprite style, JSON export/import.
|
colour, sprite style, JSON export/import.
|
||||||
- **PWA** — Workbox SW: precache shell + snapshot, SWR for API JSON,
|
- **PWA** — Workbox SW: precache shell + snapshot, SWR for API JSON,
|
||||||
|
|||||||
@ -196,6 +196,41 @@ async function main() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- Moves index -------------------------------------------
|
||||||
|
const moveIndex = await api('move?limit=100000');
|
||||||
|
console.log(`Fetching ${moveIndex.results.length} moves …`);
|
||||||
|
const moves = await mapLimit(moveIndex.results, CONCURRENCY, async (r) => {
|
||||||
|
const m = await api(r.url);
|
||||||
|
return {
|
||||||
|
id: m.id,
|
||||||
|
name: m.name,
|
||||||
|
type: m.type?.name || 'normal',
|
||||||
|
damageClass: m.damage_class?.name || null,
|
||||||
|
power: m.power,
|
||||||
|
accuracy: m.accuracy,
|
||||||
|
pp: m.pp,
|
||||||
|
priority: m.priority,
|
||||||
|
generation: idFromUrl(m.generation.url),
|
||||||
|
// version-group -> machine id (resolve to TM/HM number on demand)
|
||||||
|
machines: (m.machines || []).map((x) => ({
|
||||||
|
vg: x.version_group.name,
|
||||||
|
id: idFromUrl(x.machine.url),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
moves.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
|
// ---- Item categories (gives every item a category cheaply) ----
|
||||||
|
const catIndex = await api('item-category?limit=100');
|
||||||
|
const items = [];
|
||||||
|
await mapLimit(catIndex.results, CONCURRENCY, async (c) => {
|
||||||
|
const cat = await api(c.name.startsWith('http') ? c.name : `item-category/${c.name}`);
|
||||||
|
for (const it of cat.items) {
|
||||||
|
items.push({ id: idFromUrl(it.url), name: it.name, category: cat.name });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
items.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
// ---- Write ----------------------------------------------------
|
// ---- Write ----------------------------------------------------
|
||||||
const snapshot = {
|
const snapshot = {
|
||||||
meta: {
|
meta: {
|
||||||
@ -203,6 +238,8 @@ async function main() {
|
|||||||
source: BASE,
|
source: BASE,
|
||||||
counts: {
|
counts: {
|
||||||
species: species.length,
|
species: species.length,
|
||||||
|
moves: moves.length,
|
||||||
|
items: items.length,
|
||||||
pokedexes: pokedexes.length,
|
pokedexes: pokedexes.length,
|
||||||
versionGroups: versionGroups.length,
|
versionGroups: versionGroups.length,
|
||||||
},
|
},
|
||||||
@ -210,6 +247,8 @@ async function main() {
|
|||||||
types,
|
types,
|
||||||
generations,
|
generations,
|
||||||
species,
|
species,
|
||||||
|
moves,
|
||||||
|
items,
|
||||||
pokedexes,
|
pokedexes,
|
||||||
versionGroups,
|
versionGroups,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,6 +20,11 @@ export async function loadSnapshot() {
|
|||||||
data.pokedexByKey = new Map(data.pokedexes.map((d) => [d.key, d]));
|
data.pokedexByKey = new Map(data.pokedexes.map((d) => [d.key, d]));
|
||||||
data.versionGroupByKey = new Map(data.versionGroups.map((v) => [v.key, v]));
|
data.versionGroupByKey = new Map(data.versionGroups.map((v) => [v.key, v]));
|
||||||
data.generationById = new Map(data.generations.map((g) => [g.id, g]));
|
data.generationById = new Map(data.generations.map((g) => [g.id, g]));
|
||||||
|
data.moves = data.moves || [];
|
||||||
|
data.items = data.items || [];
|
||||||
|
data.moveById = new Map(data.moves.map((m) => [m.id, m]));
|
||||||
|
data.itemById = new Map(data.items.map((it) => [it.id, it]));
|
||||||
|
data.itemCategories = [...new Set(data.items.map((it) => it.category))].sort();
|
||||||
|
|
||||||
cached = data;
|
cached = data;
|
||||||
return cached;
|
return cached;
|
||||||
|
|||||||
@ -18,4 +18,10 @@ export const ui = createStore('pdx.ui', {
|
|||||||
recent: [],
|
recent: [],
|
||||||
toolsOpen: false,
|
toolsOpen: false,
|
||||||
teamMode: 'coverage',
|
teamMode: 'coverage',
|
||||||
|
mvType: '',
|
||||||
|
mvClass: '',
|
||||||
|
mvSort: 'name',
|
||||||
|
mvTm: false,
|
||||||
|
itCat: '',
|
||||||
|
itSort: 'name',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2274,3 +2274,34 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Lookup filter bar ------------------------------------- */
|
||||||
|
.lookup__filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.lookup__filters .feed-sort__select {
|
||||||
|
flex: 1 1 8rem;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
.lookup__check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dim);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.search__cat {
|
||||||
|
flex: none;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dim);
|
||||||
|
background: var(--surface-2);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,19 +4,19 @@ import { settings } from '../store/settings.js';
|
|||||||
import { ui } from '../store/ui.js';
|
import { ui } from '../store/ui.js';
|
||||||
import { entry } from '../store/selection.js';
|
import { entry } from '../store/selection.js';
|
||||||
import { prettify } from '../data/pokedex-resolver.js';
|
import { prettify } from '../data/pokedex-resolver.js';
|
||||||
import { getMoveIndex, getItemIndex, getMove, mapLimit } from '../data/api.js';
|
import { getMachine, mapLimit } from '../data/api.js';
|
||||||
import { Sprite } from '../components/Sprite.js';
|
import { Sprite } from '../components/Sprite.js';
|
||||||
import { TypeChip } from '../components/TypeChip.js';
|
import { TypeChip } from '../components/TypeChip.js';
|
||||||
|
|
||||||
const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop());
|
const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop());
|
||||||
// Names in the API use hyphens; people type spaces. Compare both loosely.
|
|
||||||
const loose = (s) => s.toLowerCase().replace(/[-\s]+/g, ' ').trim();
|
const loose = (s) => s.toLowerCase().replace(/[-\s]+/g, ' ').trim();
|
||||||
|
const DMG = { physical: 'Phys', special: 'Spec', status: 'Stat' };
|
||||||
|
const TYPES = [
|
||||||
|
'normal', 'fire', 'water', 'electric', 'grass', 'ice', 'fighting', 'poison',
|
||||||
|
'ground', 'flying', 'psychic', 'bug', 'rock', 'ghost', 'dragon', 'dark', 'steel', 'fairy',
|
||||||
|
];
|
||||||
const itemSprite = (name) =>
|
const itemSprite = (name) =>
|
||||||
`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/${name}.png`;
|
`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/${name}.png`;
|
||||||
const DMG = { physical: 'Phys', special: 'Spec', status: 'Stat' };
|
|
||||||
|
|
||||||
let moveIndex = null;
|
|
||||||
let itemIndex = null;
|
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: 'pokemon', label: 'Pokémon' },
|
{ id: 'pokemon', label: 'Pokémon' },
|
||||||
@ -24,11 +24,11 @@ const TABS = [
|
|||||||
{ id: 'items', label: 'Items' },
|
{ id: 'items', label: 'Items' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
const tmLabel = (raw) => {
|
||||||
* Unified lookup for Pokémon, moves and items. Pokémon come from the bundled
|
const m = raw.match(/^([a-z]+)0*(\d+)$/i);
|
||||||
* snapshot (offline); moves and items lazy-load a name index from PokéAPI
|
return m ? `${m[1].toUpperCase()}${String(Number(m[2])).padStart(2, '0')}` : raw.toUpperCase();
|
||||||
* (then cached by the service worker) and fetch per-entry detail on demand.
|
};
|
||||||
*/
|
|
||||||
export async function SearchView() {
|
export async function SearchView() {
|
||||||
const snap = await loadSnapshot();
|
const snap = await loadSnapshot();
|
||||||
const view = el('section', { class: 'view search' });
|
const view = el('section', { class: 'view search' });
|
||||||
@ -62,7 +62,7 @@ export async function SearchView() {
|
|||||||
tab = t.id;
|
tab = t.id;
|
||||||
ui.set({ searchTab: t.id });
|
ui.set({ searchTab: t.id });
|
||||||
[...seg.children].forEach((b, i) => b.classList.toggle('is-active', TABS[i].id === tab));
|
[...seg.children].forEach((b, i) => b.classList.toggle('is-active', TABS[i].id === tab));
|
||||||
syncPlaceholder();
|
syncUI();
|
||||||
run();
|
run();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -71,32 +71,94 @@ export async function SearchView() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filters = el('div', { class: 'lookup__filters' });
|
||||||
const note = el('p', { class: 'search__note' });
|
const note = el('p', { class: 'search__note' });
|
||||||
const results = el('div', { class: 'search__results' });
|
const results = el('div', { class: 'search__results' });
|
||||||
|
|
||||||
function syncPlaceholder() {
|
// ---- filter controls (rebuilt per tab) ---------------------------
|
||||||
|
function pill(label, options, value, onChange) {
|
||||||
|
return el(
|
||||||
|
'select',
|
||||||
|
{ class: 'feed-sort__select', onchange: (e) => onChange(e.target.value) },
|
||||||
|
...options.map(([v, t]) => el('option', { value: v, selected: v === value }, t)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFilters() {
|
||||||
|
clear(filters);
|
||||||
|
if (tab === 'moves') {
|
||||||
|
filters.append(
|
||||||
|
pill('mvType', [['', 'Any type'], ...TYPES.map((t) => [t, prettify(t)])], ui.get().mvType, (v) => {
|
||||||
|
ui.set({ mvType: v });
|
||||||
|
run();
|
||||||
|
}),
|
||||||
|
pill(
|
||||||
|
'mvClass',
|
||||||
|
[['', 'Any category'], ['physical', 'Physical'], ['special', 'Special'], ['status', 'Status']],
|
||||||
|
ui.get().mvClass,
|
||||||
|
(v) => {
|
||||||
|
ui.set({ mvClass: v });
|
||||||
|
run();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
pill(
|
||||||
|
'mvSort',
|
||||||
|
[['name', 'A–Z'], ['power', 'Power'], ['accuracy', 'Accuracy'], ['gen', 'Newest']],
|
||||||
|
ui.get().mvSort,
|
||||||
|
(v) => {
|
||||||
|
ui.set({ mvSort: v });
|
||||||
|
run();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
el(
|
||||||
|
'label',
|
||||||
|
{ class: 'lookup__check' },
|
||||||
|
el('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: !!ui.get().mvTm,
|
||||||
|
onchange: (e) => {
|
||||||
|
ui.set({ mvTm: e.target.checked });
|
||||||
|
run();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'TMs only',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (tab === 'items') {
|
||||||
|
filters.append(
|
||||||
|
pill(
|
||||||
|
'itCat',
|
||||||
|
[['', 'Any category'], ...snap.itemCategories.map((c) => [c, prettify(c)])],
|
||||||
|
ui.get().itCat,
|
||||||
|
(v) => {
|
||||||
|
ui.set({ itCat: v });
|
||||||
|
run();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
pill('itSort', [['name', 'A–Z'], ['id', 'Dex order']], ui.get().itSort, (v) => {
|
||||||
|
ui.set({ itSort: v });
|
||||||
|
run();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncUI() {
|
||||||
input.placeholder =
|
input.placeholder =
|
||||||
tab === 'pokemon' ? 'Name, number or type…' : tab === 'moves' ? 'Move name…' : 'Item name…';
|
tab === 'pokemon' ? 'Name, number or type…' : tab === 'moves' ? 'Move name…' : 'Item name…';
|
||||||
const st = settings.get();
|
buildFilters();
|
||||||
note.textContent =
|
|
||||||
tab === 'pokemon'
|
|
||||||
? `Every Pokémon, any game. Opening one shows it for ${
|
|
||||||
st.versionGroup === 'all' ? 'all games' : prettify(st.versionGroup)
|
|
||||||
}.`
|
|
||||||
: tab === 'moves'
|
|
||||||
? 'Every move in the series.'
|
|
||||||
: 'Every item in the series.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view.append(
|
view.append(
|
||||||
el('header', { class: 'view__header' }, el('h1', {}, 'Search'), note),
|
el('header', { class: 'view__header' }, el('h1', {}, 'Search'), note),
|
||||||
seg,
|
seg,
|
||||||
|
filters,
|
||||||
input,
|
input,
|
||||||
results,
|
results,
|
||||||
);
|
);
|
||||||
syncPlaceholder();
|
syncUI();
|
||||||
|
|
||||||
// ---- renderers -------------------------------------------------
|
// ---- Pokémon -------------------------------------------------
|
||||||
function renderPokemon(q) {
|
function renderPokemon(q) {
|
||||||
const style = settings.get().spriteStyle;
|
const style = settings.get().spriteStyle;
|
||||||
const matches = snap.species
|
const matches = snap.species
|
||||||
@ -123,100 +185,121 @@ export async function SearchView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderMoves(q, token) {
|
// ---- Moves (from snapshot, offline) -----------------------------
|
||||||
if (!moveIndex) {
|
function renderMoves(q, token) {
|
||||||
results.append(el('p', { class: 'search__empty' }, 'Loading moves…'));
|
const st = settings.get();
|
||||||
try {
|
const vg = st.versionGroup;
|
||||||
moveIndex = (await getMoveIndex()).results;
|
const type = ui.get().mvType;
|
||||||
} catch {
|
const cls = ui.get().mvClass;
|
||||||
if (token === runToken) results.textContent = 'Move list unavailable offline.';
|
const sort = ui.get().mvSort;
|
||||||
return;
|
const tmOnly = !!ui.get().mvTm;
|
||||||
}
|
|
||||||
if (token !== runToken) return; // superseded by a newer query/tab
|
|
||||||
clear(results);
|
|
||||||
}
|
|
||||||
const matches = moveIndex.filter((m) => loose(m.name).includes(q)).slice(0, 80);
|
|
||||||
if (!matches.length) return void results.append(el('p', { class: 'search__empty' }, 'No matches.'));
|
|
||||||
|
|
||||||
const metas = new Map();
|
let list = snap.moves.filter((m) => {
|
||||||
for (const m of matches) {
|
if (q && !loose(m.name).includes(q)) return false;
|
||||||
const meta = el('span', { class: 'search__types' });
|
if (type && m.type !== type) return false;
|
||||||
metas.set(m.name, meta);
|
if (cls && m.damageClass !== cls) return false;
|
||||||
|
if (tmOnly && !m.machines.some((x) => vg === 'all' || x.vg === vg)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
list.sort((a, b) => {
|
||||||
|
if (sort === 'power') return (b.power ?? -1) - (a.power ?? -1) || a.name.localeCompare(b.name);
|
||||||
|
if (sort === 'accuracy') return (b.accuracy ?? -1) - (a.accuracy ?? -1) || a.name.localeCompare(b.name);
|
||||||
|
if (sort === 'gen') return b.generation - a.generation || a.name.localeCompare(b.name);
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
note.textContent = `${list.length} move${list.length === 1 ? '' : 's'}`;
|
||||||
|
const shown = list.slice(0, 400);
|
||||||
|
const machineCells = new Map();
|
||||||
|
|
||||||
|
for (const m of shown) {
|
||||||
|
const tmCell = tmOnly ? el('span', { class: 'moverow__tm' }) : null;
|
||||||
|
if (tmCell) machineCells.set(m.id, { cell: tmCell, m });
|
||||||
results.append(
|
results.append(
|
||||||
el(
|
el(
|
||||||
'a',
|
'a',
|
||||||
{ class: 'search__row search__row--slim', href: `#/move/${idFromUrl(m.url)}` },
|
{ class: 'search__row search__row--slim', href: `#/move/${m.id}` },
|
||||||
|
tmCell,
|
||||||
el('span', { class: 'search__name' }, m.name.replace(/-/g, ' ')),
|
el('span', { class: 'search__name' }, m.name.replace(/-/g, ' ')),
|
||||||
meta,
|
el(
|
||||||
|
'span',
|
||||||
|
{ class: 'search__types' },
|
||||||
|
TypeChip(m.type),
|
||||||
|
el('span', { class: 'moverow__cat', dataset: { cat: m.damageClass || '' } }, DMG[m.damageClass] || '—'),
|
||||||
|
el('span', { class: 'search__num' }, m.power != null ? `${m.power} pw` : ''),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Enrich a short result set with type / class / power after a pause.
|
if (list.length > shown.length) {
|
||||||
clearTimeout(enrichTimer);
|
results.append(el('p', { class: 'search__empty' }, `Showing ${shown.length} of ${list.length} — refine to see more.`));
|
||||||
if (matches.length <= 24) {
|
}
|
||||||
|
|
||||||
|
if (tmOnly && machineCells.size) {
|
||||||
|
clearTimeout(enrichTimer);
|
||||||
enrichTimer = setTimeout(() => {
|
enrichTimer = setTimeout(() => {
|
||||||
mapLimit(matches, 6, async (m) => {
|
mapLimit([...machineCells.values()], 6, async ({ cell, m }) => {
|
||||||
|
if (token !== runToken || !cell.isConnected) return;
|
||||||
|
const hit = m.machines.find((x) => x.vg === vg) || m.machines[m.machines.length - 1];
|
||||||
|
if (!hit) return;
|
||||||
try {
|
try {
|
||||||
const d = await getMove(idFromUrl(m.url));
|
const mc = await getMachine(hit.id);
|
||||||
const meta = metas.get(m.name);
|
cell.textContent = tmLabel(mc.item.name);
|
||||||
if (!meta.isConnected) return;
|
|
||||||
meta.append(
|
|
||||||
TypeChip(d.type.name),
|
|
||||||
el('span', { class: 'moverow__cat', dataset: { cat: d.damage_class?.name || '' } },
|
|
||||||
DMG[d.damage_class?.name] || '—'),
|
|
||||||
el('span', { class: 'search__num' }, d.power != null ? `${d.power} pw` : ''),
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
/* leave bare */
|
/* leave blank */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 220);
|
}, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderItems(q, token) {
|
// ---- Items (from snapshot, offline) ---------------------------
|
||||||
if (!itemIndex) {
|
function renderItems(q) {
|
||||||
results.append(el('p', { class: 'search__empty' }, 'Loading items…'));
|
const cat = ui.get().itCat;
|
||||||
try {
|
const sort = ui.get().itSort;
|
||||||
itemIndex = (await getItemIndex()).results;
|
let list = snap.items.filter((it) => {
|
||||||
} catch {
|
if (q && !loose(it.name).includes(q)) return false;
|
||||||
if (token === runToken) results.textContent = 'Item list unavailable offline.';
|
if (cat && it.category !== cat) return false;
|
||||||
return;
|
return true;
|
||||||
}
|
});
|
||||||
if (token !== runToken) return;
|
list.sort((a, b) => (sort === 'id' ? a.id - b.id : a.name.localeCompare(b.name)));
|
||||||
clear(results);
|
|
||||||
}
|
note.textContent = `${list.length} item${list.length === 1 ? '' : 's'}`;
|
||||||
const matches = itemIndex.filter((it) => loose(it.name).includes(q)).slice(0, 80);
|
const shown = list.slice(0, 400);
|
||||||
if (!matches.length) return void results.append(el('p', { class: 'search__empty' }, 'No matches.'));
|
for (const it of shown) {
|
||||||
for (const it of matches) {
|
const icon = el('img', { class: 'search__item-icon', loading: 'lazy', alt: '', src: itemSprite(it.name) });
|
||||||
const icon = el('img', {
|
|
||||||
class: 'search__item-icon',
|
|
||||||
loading: 'lazy',
|
|
||||||
alt: '',
|
|
||||||
src: itemSprite(it.name),
|
|
||||||
});
|
|
||||||
icon.addEventListener('error', () => icon.remove(), { once: true });
|
icon.addEventListener('error', () => icon.remove(), { once: true });
|
||||||
results.append(
|
results.append(
|
||||||
el(
|
el(
|
||||||
'a',
|
'a',
|
||||||
{ class: 'search__row', href: `#/item/${idFromUrl(it.url)}` },
|
{ class: 'search__row', href: `#/item/${it.id}` },
|
||||||
icon,
|
icon,
|
||||||
el('span', { class: 'search__name' }, it.name.replace(/-/g, ' ')),
|
el('span', { class: 'search__name' }, it.name.replace(/-/g, ' ')),
|
||||||
|
el('span', { class: 'search__cat' }, prettify(it.category)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (list.length > shown.length) {
|
||||||
|
results.append(el('p', { class: 'search__empty' }, `Showing ${shown.length} of ${list.length} — refine to see more.`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
const token = ++runToken;
|
const token = ++runToken;
|
||||||
const q = loose(query);
|
const q = loose(query);
|
||||||
clear(results);
|
clear(results);
|
||||||
|
note.textContent = '';
|
||||||
if (tab === 'pokemon') {
|
if (tab === 'pokemon') {
|
||||||
|
const st = settings.get();
|
||||||
|
note.textContent = `Every Pokémon, any game. Opens for ${
|
||||||
|
st.versionGroup === 'all' ? 'all games' : prettify(st.versionGroup)
|
||||||
|
}.`;
|
||||||
if (q) renderPokemon(q);
|
if (q) renderPokemon(q);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!q) return;
|
if (tab === 'moves') renderMoves(q, token);
|
||||||
(tab === 'moves' ? renderMoves : renderItems)(q, token);
|
else renderItems(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user