diff --git a/README.md b/README.md index 53c267a..4f5fe6a 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,9 @@ Every game's seen/caught tally against its own dex, from its own per-game bucket. Leads with the games you've played (selected at least once, or imported a save for); the rest sit behind a toggle. The National Dex row is the union across all games. **Edit** the "Your games" list to reset a -game's caught/seen or drop it from the list. +game's caught/seen or drop it from the list; **Reset caught / seen…** wipes +every game at once (favorites and notes are kept — only Settings → +_Clear tracking data_ removes those). ### Shiny hunt tracker (`#/shiny`) diff --git a/src/store/selection.js b/src/store/selection.js index 3e73b3c..50a7090 100644 --- a/src/store/selection.js +++ b/src/store/selection.js @@ -149,6 +149,15 @@ export function clearGame(vgKey) { }); } +/** + * Wipe seen/caught for every game (a "National Dex reset"). Favorites and + * notes — the global `pokemon` record — are kept. Use `selection.replace` + * for the full wipe that also drops those. + */ +export function clearAllGames() { + selection.set((s) => ({ ...s, games: {} })); +} + /** Aggregate seen/caught over a species list for one game (union when 'all'). */ export function stats(speciesIds, vgKey = curGame()) { const get = vgKey === 'all' ? unionEntry : (id) => selection.get().games[vgKey]?.[id] || GBLANK; diff --git a/src/styles/layout.css b/src/styles/layout.css index a9700f5..323035e 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -3382,6 +3382,13 @@ color: var(--text-dim); text-align: right; } +.prog__bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + flex-wrap: wrap; +} .prog__legend { display: flex; align-items: center; @@ -3389,6 +3396,19 @@ font-size: 0.75rem; color: var(--text-dim); } +.prog__resetall { + border: none; + background: none; + font: inherit; + font-size: 0.78rem; + font-weight: 600; + color: var(--danger); + cursor: pointer; + padding: 4px 0; +} +.prog__resetall:hover { + text-decoration: underline; +} .prog__key { display: inline-block; width: 1.1rem; diff --git a/src/views/ProgressView.js b/src/views/ProgressView.js index 3508aa5..3fc2e1d 100644 --- a/src/views/ProgressView.js +++ b/src/views/ProgressView.js @@ -1,7 +1,7 @@ import { el, clear, onTeardown } from '../lib/dom.js'; import { loadSnapshot } from '../data/snapshot.js'; import { settings } from '../store/settings.js'; -import { selection, stats, statsNational, clearGame } from '../store/selection.js'; +import { selection, stats, statsNational, clearGame, clearAllGames } from '../store/selection.js'; import { ui } from '../store/ui.js'; import { playedGames, isPlayed, unmarkPlayed } from '../store/playedGames.js'; import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js'; @@ -172,6 +172,23 @@ export async function ProgressView() { } render(); + async function resetEverything() { + const nat = statsNational(snap.species.map((s) => s.id)); + if (!nat.seen && !nat.caught) return; + const pick = await chooseDialog({ + title: 'Reset caught / seen', + body: `Clear all ${nat.caught} caught and ${nat.seen} seen, across every game? Favorites and notes are kept.`, + choices: [ + { key: 'reset', label: 'Reset everything', class: 'button--danger' }, + { key: null, label: 'Cancel', class: 'button--ghost' }, + ], + }); + if (pick === 'reset') { + clearAllGames(); + render(); + } + } + clear(view).append( el( 'nav', @@ -185,12 +202,21 @@ export async function ProgressView() { el('p', {}, 'What you’ve caught in each game. Tap one to switch to it.'), ), el( - 'p', - { class: 'prog__legend' }, - el('span', { class: 'prog__key prog__key--caught' }), - ' caught ', - el('span', { class: 'prog__key prog__key--seen' }), - ' seen', + 'div', + { class: 'prog__bar' }, + el( + 'p', + { class: 'prog__legend' }, + el('span', { class: 'prog__key prog__key--caught' }), + ' caught ', + el('span', { class: 'prog__key prog__key--seen' }), + ' seen', + ), + el( + 'button', + { type: 'button', class: 'prog__resetall', onclick: resetEverything }, + 'Reset caught / seen…', + ), ), list, ); diff --git a/src/views/SettingsView.js b/src/views/SettingsView.js index a19fb7f..5a21d9d 100644 --- a/src/views/SettingsView.js +++ b/src/views/SettingsView.js @@ -223,9 +223,17 @@ export async function SettingsView() { if (data.formTracking) formTracking.replace(data.formTracking); if (data.shinyHunts) shinyHunts.replace(data.shinyHunts); if (data.playedGames) playedGames.replace(data.playedGames); - alert('Import complete.'); + await chooseDialog({ + title: 'Import complete', + body: 'Your backup was restored.', + choices: [{ key: null, label: 'OK' }], + }); } catch { - alert('That file could not be read as a Pokédex backup.'); + await chooseDialog({ + title: 'Couldn’t read that file', + body: 'It doesn’t look like a Pocketdex backup.', + choices: [{ key: null, label: 'OK' }], + }); } }, }); @@ -322,10 +330,16 @@ export async function SettingsView() { { class: 'button button--danger', type: 'button', - onclick: () => { - if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) { - selection.replace({ version: 3, pokemon: {}, games: {} }); - } + onclick: async () => { + const pick = await chooseDialog({ + title: 'Clear all tracking data', + body: 'Removes seen/caught for every game plus all favorites and notes, and can’t be undone. To keep favorites and notes, use “Reset caught / seen” on the Progress screen instead.', + choices: [ + { key: 'clear', label: 'Clear everything', class: 'button--danger' }, + { key: null, label: 'Cancel', class: 'button--ghost' }, + ], + }); + if (pick === 'clear') selection.replace({ version: 3, pokemon: {}, games: {} }); }, }, 'Clear tracking data', @@ -337,12 +351,21 @@ export async function SettingsView() { type: 'button', onclick: async () => { if (!('caches' in window)) return; - if ( - confirm('Clear cached PokéAPI data and sprites? They will re-download when online.') - ) { - for (const key of await caches.keys()) await caches.delete(key); - alert('Caches cleared.'); - } + const pick = await chooseDialog({ + title: 'Clear cached data', + body: 'Drops cached PokéAPI responses and sprites. They re-download next time you’re online.', + choices: [ + { key: 'clear', label: 'Clear cache', class: 'button--danger' }, + { key: null, label: 'Cancel', class: 'button--ghost' }, + ], + }); + if (pick !== 'clear') return; + for (const key of await caches.keys()) await caches.delete(key); + await chooseDialog({ + title: 'Cache cleared', + body: '', + choices: [{ key: null, label: 'OK' }], + }); }, }, 'Clear cached API data', diff --git a/src/views/ShinyView.js b/src/views/ShinyView.js index 68cb5d8..a5a4497 100644 --- a/src/views/ShinyView.js +++ b/src/views/ShinyView.js @@ -6,6 +6,7 @@ import { toggle, entry } from '../store/selection.js'; import { buzz } from '../lib/haptics.js'; import { Sprite } from '../components/Sprite.js'; import { openPokemonPicker } from '../components/PokemonPicker.js'; +import { chooseDialog } from '../lib/dialog.js'; // Representative full-game odds (as 1/N), with a shiny-charm variant. export const METHODS = [ @@ -131,8 +132,16 @@ export async function ShinyView() { { type: 'button', class: 'button button--danger', - onclick: () => { - if (confirm('Delete this hunt?')) { + onclick: async () => { + const pick = await chooseDialog({ + title: 'Delete this hunt?', + body: `${(sp?.name || 'This hunt').replace(/-/g, ' ')} — ${h.count} checks logged.`, + choices: [ + { key: 'del', label: 'Delete', class: 'button--danger' }, + { key: null, label: 'Cancel', class: 'button--ghost' }, + ], + }); + if (pick === 'del') { removeHunt(h.id); render(); }