Track seen/caught per game, not one unified record
Catching a Pokémon in Blue was counting it as caught in Gold, Emerald and
every other game, because the whole app ran on one caught record keyed by
National Dex id. Progress by game just intersected that set with each
game's species list, so the per-game bars were meaningless.
selection store is now:
pokemon: { [id]: { favorite, note } } -- global (describe the mon)
games: { [vg]: { [id]: { seen, caught } } } -- per game
- entry(id) / toggle(id, field) / stats(ids) default to the selected game;
favorite/note stay global. 'all' (All-games mode) reads as the union of
every game -- "caught anywhere" -- and is a real bucket you write to when
no specific game is selected.
- v2 stores migrate on load (normalizeSelection): favorite/note lift out,
seen/caught drop into the 'all' bucket so nothing is falsely attributed
to a game. Same path runs on JSON backup import.
- ProgressView: each game row counts its own bucket; National row =
statsNational (union). Intro text slimmed.
- Save import writes into one game bucket -- the selected game if it's a
candidate for that save, else the parser's first guess -- and the dialog
names which game it's importing to.
- DexGrid feed (ring, filter counts, caught-sort), PokemonDetail track
buttons, search dots, shiny "found it" all follow the selected game.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
31a00fdf52
commit
c1f6007b25
@ -9,7 +9,7 @@ framework.
|
|||||||
| Feature | How it works |
|
| Feature | How it works |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Game series selection** | Pick a *game* (PokéAPI version group, e.g. Scarlet & Violet). If it has more than one regional dex (Paldea / Kitakami / Blueberry) a sub-dex switcher appears. The choice drives which species show, their regional numbering, and which version's flavor text the detail page uses. Stored in `localStorage`. |
|
| **Game series selection** | Pick a *game* (PokéAPI version group, e.g. Scarlet & Violet). If it has more than one regional dex (Paldea / Kitakami / Blueberry) a sub-dex switcher appears. The choice drives which species show, their regional numbering, and which version's flavor text the detail page uses. Stored in `localStorage`. |
|
||||||
| **Unified Pokémon selection** | One tracking record per Pokémon, keyed by **National Dex id**. Mark Pikachu once and every game's view reflects it — each dex computes its own "Seen / Caught" totals by intersecting its species list with that single map. `#/progress` breaks that down per game, leading with the ones you've actually played (selected, or imported a save for) and tucking the rest behind a toggle. The active Pokémon is also a single deep-linkable route (`#/pokemon/25`). |
|
| **Per-game Pokémon tracking** | Seen / caught are stored **per game** (version-group key) — catching Pikachu in Blue says nothing about your Gold save. `favorite` and `note` are global (they describe the Pokémon, keyed by National Dex id). The `all` bucket ("All games" mode) reads as the union — "caught anywhere" — and is where a pre-per-game save or an older backup migrates to. `#/progress` shows each game's own tally (National = the union), leading with the games you've played and tucking the rest behind a toggle. The active Pokémon is also a single deep-linkable route (`#/pokemon/25`). |
|
||||||
|
|
||||||
## Data & storage split
|
## Data & storage split
|
||||||
|
|
||||||
|
|||||||
@ -1,98 +1,160 @@
|
|||||||
import { createStore } from './createStore.js';
|
import { createStore } from './createStore.js';
|
||||||
|
import { settings } from './settings.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "unified Pokémon selection".
|
* Per-game Pokémon tracking.
|
||||||
*
|
*
|
||||||
* One record per Pokémon, keyed by NATIONAL dex id. That key is what makes
|
* Seen / caught are stored *per game* (PokéAPI version-group key) — catching
|
||||||
* it unified: mark Pikachu once and every game's view — Kanto, Paldea,
|
* Pikachu in Blue says nothing about your Gold save. `favorite` and `note`
|
||||||
* National, search results — reflects it, because each view computes its
|
* are the exception: those describe the Pokémon itself, so they're global,
|
||||||
* own progress by intersecting its species list with this single map.
|
* one record keyed by National dex id.
|
||||||
|
*
|
||||||
|
* pokemon: { [id]: { favorite, note, updatedAt } } // global
|
||||||
|
* games: { [vgKey]: { [id]: { seen, caught, updatedAt } } } // per game
|
||||||
|
*
|
||||||
|
* The 'all' key ("All games" / National mode) is special: reading it returns
|
||||||
|
* the union of every game — "have I caught this anywhere". It's also a real
|
||||||
|
* bucket you write to when you mark something while no specific game is
|
||||||
|
* selected (and where a pre-per-game save's data was migrated to).
|
||||||
*/
|
*/
|
||||||
export const selection = createStore('pdx.selection', {
|
export const selection = createStore('pdx.selection', {
|
||||||
version: 2,
|
version: 3,
|
||||||
pokemon: {},
|
pokemon: {},
|
||||||
|
games: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const BLANK = { seen: false, caught: false, favorite: false, note: '' };
|
const GBLANK = { seen: false, caught: false };
|
||||||
|
const PBLANK = { favorite: false, note: '' };
|
||||||
|
|
||||||
export function entry(id) {
|
/** Convert any older (flat, one-record) shape to the current per-game one. */
|
||||||
return selection.get().pokemon[id] || BLANK;
|
export function normalizeSelection(raw) {
|
||||||
|
if (!raw || typeof raw !== 'object') return { version: 3, pokemon: {}, games: {} };
|
||||||
|
if (raw.version >= 3 && raw.games) {
|
||||||
|
return { version: 3, pokemon: raw.pokemon || {}, games: raw.games || {} };
|
||||||
|
}
|
||||||
|
const pokemon = {};
|
||||||
|
const all = {};
|
||||||
|
for (const [id, e] of Object.entries(raw.pokemon || {})) {
|
||||||
|
if (e.favorite || (e.note && String(e.note).trim())) {
|
||||||
|
pokemon[id] = { favorite: !!e.favorite, note: e.note || '', updatedAt: e.updatedAt };
|
||||||
|
}
|
||||||
|
if (e.seen || e.caught) {
|
||||||
|
all[id] = { seen: !!e.seen, caught: !!e.caught, updatedAt: e.updatedAt };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { version: 3, pokemon, games: Object.keys(all).length ? { all } : {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggle(id, field) {
|
// One-time migration of an existing v2 store on this device.
|
||||||
|
{
|
||||||
|
const cur = selection.get();
|
||||||
|
if (!cur || cur.version < 3 || !cur.games) selection.replace(normalizeSelection(cur));
|
||||||
|
}
|
||||||
|
|
||||||
|
function curGame() {
|
||||||
|
return settings.get().versionGroup || 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
function unionEntry(id) {
|
||||||
|
const games = selection.get().games;
|
||||||
|
let seen = false;
|
||||||
|
let caught = false;
|
||||||
|
let updatedAt = '';
|
||||||
|
for (const k in games) {
|
||||||
|
const e = games[k][id];
|
||||||
|
if (!e) continue;
|
||||||
|
if (e.seen || e.caught) seen = true;
|
||||||
|
if (e.caught) caught = true;
|
||||||
|
if (e.updatedAt && e.updatedAt > updatedAt) updatedAt = e.updatedAt;
|
||||||
|
}
|
||||||
|
return { seen, caught, updatedAt };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Raw seen/caught for one game (union when vgKey === 'all'). */
|
||||||
|
export function gameEntry(id, vgKey = curGame()) {
|
||||||
|
if (vgKey === 'all') return unionEntry(id);
|
||||||
|
return selection.get().games[vgKey]?.[id] || GBLANK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Merged view for a game: its seen/caught + the global favorite/note. */
|
||||||
|
export function entry(id, vgKey = curGame()) {
|
||||||
|
const g = gameEntry(id, vgKey);
|
||||||
|
const p = selection.get().pokemon[id] || PBLANK;
|
||||||
|
return { seen: !!g.seen, caught: !!g.caught, favorite: !!p.favorite, note: p.note || '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggle(id, field, vgKey = curGame()) {
|
||||||
selection.set((s) => {
|
selection.set((s) => {
|
||||||
const current = s.pokemon[id] || BLANK;
|
const stamp = new Date().toISOString();
|
||||||
const next = {
|
if (field === 'favorite') {
|
||||||
...current,
|
const cur = s.pokemon[id] || PBLANK;
|
||||||
[field]: !current[field],
|
return {
|
||||||
updatedAt: new Date().toISOString(),
|
...s,
|
||||||
};
|
pokemon: { ...s.pokemon, [id]: { ...cur, favorite: !cur.favorite, updatedAt: stamp } },
|
||||||
// Catching something implies you've seen it.
|
};
|
||||||
|
}
|
||||||
|
const bucket = s.games[vgKey] || {};
|
||||||
|
const cur = bucket[id] || GBLANK;
|
||||||
|
const next = { ...cur, [field]: !cur[field], updatedAt: stamp };
|
||||||
if (field === 'caught' && next.caught) next.seen = true;
|
if (field === 'caught' && next.caught) next.seen = true;
|
||||||
return { ...s, pokemon: { ...s.pokemon, [id]: next } };
|
return { ...s, games: { ...s.games, [vgKey]: { ...bucket, [id]: next } } };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setNote(id, note) {
|
export function setNote(id, note) {
|
||||||
selection.set((s) => {
|
selection.set((s) => {
|
||||||
const current = s.pokemon[id] || BLANK;
|
const cur = s.pokemon[id] || PBLANK;
|
||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
pokemon: {
|
pokemon: { ...s.pokemon, [id]: { ...cur, note, updatedAt: new Date().toISOString() } },
|
||||||
...s.pokemon,
|
|
||||||
[id]: { ...current, note, updatedAt: new Date().toISOString() },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merge in seen/caught species lists (e.g. from a save-file import). Never clears. */
|
|
||||||
export function importFlags({ seen = [], caught = [] } = {}) {
|
|
||||||
selection.set((s) => {
|
|
||||||
const pokemon = { ...s.pokemon };
|
|
||||||
const stamp = new Date().toISOString();
|
|
||||||
const bump = (id, patch) => {
|
|
||||||
pokemon[id] = { ...(pokemon[id] || BLANK), ...patch, updatedAt: stamp };
|
|
||||||
};
|
|
||||||
for (const id of seen) bump(id, { seen: true });
|
|
||||||
for (const id of caught) bump(id, { seen: true, caught: true });
|
|
||||||
return { ...s, pokemon };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set seen/caught to exactly the given lists — favourites and notes are
|
* Merge seen/caught lists into one game's bucket (a save-file import).
|
||||||
* kept, everything else's seen/caught is cleared. For "replace with this
|
* Never clears. `game` defaults to the selected one.
|
||||||
* save" imports.
|
|
||||||
*/
|
*/
|
||||||
export function replaceFlags({ seen = [], caught = [] } = {}) {
|
export function importFlags({ seen = [], caught = [], game } = {}) {
|
||||||
|
const vgKey = game || curGame();
|
||||||
selection.set((s) => {
|
selection.set((s) => {
|
||||||
const stamp = new Date().toISOString();
|
const stamp = new Date().toISOString();
|
||||||
const pokemon = {};
|
const bucket = { ...(s.games[vgKey] || {}) };
|
||||||
for (const [id, e] of Object.entries(s.pokemon)) {
|
for (const id of seen) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, updatedAt: stamp };
|
||||||
if (e.favorite || (e.note && e.note.trim())) {
|
for (const id of caught) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, caught: true, updatedAt: stamp };
|
||||||
pokemon[id] = { seen: false, caught: false, favorite: !!e.favorite, note: e.note || '', updatedAt: e.updatedAt || stamp };
|
return { ...s, games: { ...s.games, [vgKey]: bucket } };
|
||||||
}
|
|
||||||
}
|
|
||||||
const bump = (id, patch) => {
|
|
||||||
pokemon[id] = { ...(pokemon[id] || BLANK), ...patch, updatedAt: stamp };
|
|
||||||
};
|
|
||||||
for (const id of seen) bump(id, { seen: true });
|
|
||||||
for (const id of caught) bump(id, { seen: true, caught: true });
|
|
||||||
return { ...s, pokemon };
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Aggregate seen/caught counts over an arbitrary list of national ids. */
|
/** Set one game's bucket to exactly these lists (a "replace with this save" import). */
|
||||||
export function stats(speciesIds) {
|
export function replaceFlags({ seen = [], caught = [], game } = {}) {
|
||||||
const p = selection.get().pokemon;
|
const vgKey = game || curGame();
|
||||||
|
selection.set((s) => {
|
||||||
|
const stamp = new Date().toISOString();
|
||||||
|
const bucket = {};
|
||||||
|
for (const id of seen) bucket[id] = { seen: true, caught: false, updatedAt: stamp };
|
||||||
|
for (const id of caught) bucket[id] = { seen: true, caught: true, updatedAt: stamp };
|
||||||
|
return { ...s, games: { ...s.games, [vgKey]: bucket } };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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;
|
||||||
let seen = 0;
|
let seen = 0;
|
||||||
let caught = 0;
|
let caught = 0;
|
||||||
for (const id of speciesIds) {
|
for (const id of speciesIds) {
|
||||||
const e = p[id];
|
const e = get(id);
|
||||||
if (!e) continue;
|
|
||||||
if (e.seen || e.caught) seen++;
|
if (e.seen || e.caught) seen++;
|
||||||
if (e.caught) caught++;
|
if (e.caught) caught++;
|
||||||
}
|
}
|
||||||
return { seen, caught, total: speciesIds.length };
|
return { seen, caught, total: speciesIds.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Caught/seen across every game — the true National Dex tally. */
|
||||||
|
export function statsNational(speciesIds) {
|
||||||
|
return stats(speciesIds, 'all');
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { loadSnapshot } from '../data/snapshot.js';
|
|||||||
import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js';
|
import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js';
|
||||||
import { settings } from '../store/settings.js';
|
import { settings } from '../store/settings.js';
|
||||||
import { ui } from '../store/ui.js';
|
import { ui } from '../store/ui.js';
|
||||||
import { selection, stats } from '../store/selection.js';
|
import { selection, stats, entry, gameEntry } from '../store/selection.js';
|
||||||
import { Card } from '../components/Card.js';
|
import { Card } from '../components/Card.js';
|
||||||
import { ProgressRing } from '../components/ProgressRing.js';
|
import { ProgressRing } from '../components/ProgressRing.js';
|
||||||
import { Sprite } from '../components/Sprite.js';
|
import { Sprite } from '../components/Sprite.js';
|
||||||
@ -56,7 +56,7 @@ function sortValue(row, key) {
|
|||||||
case 'weight': return sp.weight ?? 0;
|
case 'weight': return sp.weight ?? 0;
|
||||||
case 'baseExp': return sp.baseExp ?? 0;
|
case 'baseExp': return sp.baseExp ?? 0;
|
||||||
case 'catchrate': return sp.captureRate ?? 0;
|
case 'catchrate': return sp.captureRate ?? 0;
|
||||||
case 'caught': return selection.get().pokemon[sp.id]?.updatedAt || '';
|
case 'caught': return gameEntry(sp.id).updatedAt || '';
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,17 +404,16 @@ export async function DexGrid() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
function counts() {
|
function counts() {
|
||||||
const p = selection.get().pokemon;
|
|
||||||
let caught = 0;
|
let caught = 0;
|
||||||
let favorite = 0;
|
let favorite = 0;
|
||||||
let legendary = 0;
|
let legendary = 0;
|
||||||
let notes = 0;
|
let notes = 0;
|
||||||
for (const { species } of rows) {
|
for (const { species } of rows) {
|
||||||
const e = p[species.id];
|
const e = entry(species.id);
|
||||||
if (e?.caught) caught++;
|
if (e.caught) caught++;
|
||||||
if (e?.favorite) favorite++;
|
if (e.favorite) favorite++;
|
||||||
if (species.isLegendary || species.isMythical) legendary++;
|
if (species.isLegendary || species.isMythical) legendary++;
|
||||||
if (e?.note && e.note.trim()) notes++;
|
if (e.note && e.note.trim()) notes++;
|
||||||
}
|
}
|
||||||
return { all: rows.length, caught, missing: rows.length - caught, favorite, legendary, notes };
|
return { all: rows.length, caught, missing: rows.length - caught, favorite, legendary, notes };
|
||||||
}
|
}
|
||||||
@ -429,7 +428,6 @@ export async function DexGrid() {
|
|||||||
|
|
||||||
function paintGrid() {
|
function paintGrid() {
|
||||||
const st = settings.get();
|
const st = settings.get();
|
||||||
const pokemonState = selection.get().pokemon;
|
|
||||||
const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9;
|
const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9;
|
||||||
const boxed = st.spriteStyle === 'game' && gen <= 2;
|
const boxed = st.spriteStyle === 'game' && gen <= 2;
|
||||||
const filterTest = FILTERS.find((f) => f.key === filterKey).test;
|
const filterTest = FILTERS.find((f) => f.key === filterKey).test;
|
||||||
@ -450,8 +448,7 @@ export async function DexGrid() {
|
|||||||
if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false;
|
if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false;
|
||||||
if (filterMinBst && (species.bst || 0) < filterMinBst) return false;
|
if (filterMinBst && (species.bst || 0) < filterMinBst) return false;
|
||||||
if (filterFullyEvolved && !isFullyEvolved(species)) return false;
|
if (filterFullyEvolved && !isFullyEvolved(species)) return false;
|
||||||
const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false };
|
return filterTest(entry(species.id), species);
|
||||||
return filterTest(e, species);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dir = sortDesc ? -1 : 1;
|
const dir = sortDesc ? -1 : 1;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { el, clear, onTeardown } from '../lib/dom.js';
|
import { el, clear, onTeardown } from '../lib/dom.js';
|
||||||
import { loadSnapshot } from '../data/snapshot.js';
|
import { loadSnapshot } from '../data/snapshot.js';
|
||||||
import { settings } from '../store/settings.js';
|
import { settings } from '../store/settings.js';
|
||||||
import { selection, stats } from '../store/selection.js';
|
import { selection, stats, statsNational } from '../store/selection.js';
|
||||||
import { ui } from '../store/ui.js';
|
import { ui } from '../store/ui.js';
|
||||||
import { playedGames, isPlayed } from '../store/playedGames.js';
|
import { playedGames, isPlayed } from '../store/playedGames.js';
|
||||||
import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js';
|
import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js';
|
||||||
@ -31,7 +31,7 @@ export async function ProgressView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function gameRow(vg, { played }) {
|
function gameRow(vg, { played }) {
|
||||||
const s = stats(speciesIdsFor(vg.key));
|
const s = stats(speciesIdsFor(vg.key), vg.key);
|
||||||
return el(
|
return el(
|
||||||
'button',
|
'button',
|
||||||
{
|
{
|
||||||
@ -56,8 +56,8 @@ export async function ProgressView() {
|
|||||||
const anyPlayed = gens.some(({ versionGroups }) => versionGroups.some((vg) => isPlayed(vg.key)));
|
const anyPlayed = gens.some(({ versionGroups }) => versionGroups.some((vg) => isPlayed(vg.key)));
|
||||||
const showAll = ui.get().progShowAll || !anyPlayed;
|
const showAll = ui.get().progShowAll || !anyPlayed;
|
||||||
|
|
||||||
// National total — always shown.
|
// National total — caught in any game.
|
||||||
const nat = stats(snap.species.map((s) => s.id));
|
const nat = statsNational(snap.species.map((s) => s.id));
|
||||||
list.append(
|
list.append(
|
||||||
el(
|
el(
|
||||||
'div',
|
'div',
|
||||||
@ -120,7 +120,7 @@ export async function ProgressView() {
|
|||||||
'header',
|
'header',
|
||||||
{ class: 'view__header' },
|
{ class: 'view__header' },
|
||||||
el('h1', {}, 'Progress by game'),
|
el('h1', {}, 'Progress by game'),
|
||||||
el('p', {}, 'Your one unified caught record, counted against each game’s dex. Games you haven’t played are tucked away. Tap a game to switch to it.'),
|
el('p', {}, 'What you’ve caught in each game. Tap one to switch to it.'),
|
||||||
),
|
),
|
||||||
el(
|
el(
|
||||||
'p',
|
'p',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { el, onTeardown } from '../lib/dom.js';
|
import { el, onTeardown } from '../lib/dom.js';
|
||||||
import { settings, applyTheme } from '../store/settings.js';
|
import { settings, applyTheme } from '../store/settings.js';
|
||||||
import { selection, importFlags, replaceFlags } from '../store/selection.js';
|
import { selection, importFlags, replaceFlags, normalizeSelection } from '../store/selection.js';
|
||||||
|
import { prettify } from '../data/pokedex-resolver.js';
|
||||||
import { chooseDialog } from '../lib/dialog.js';
|
import { chooseDialog } from '../lib/dialog.js';
|
||||||
import { team } from '../store/team.js';
|
import { team } from '../store/team.js';
|
||||||
import { formTracking } from '../store/formTracking.js';
|
import { formTracking } from '../store/formTracking.js';
|
||||||
@ -144,23 +145,30 @@ export async function SettingsView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const who = dex.trainer || dex.game;
|
const who = dex.trainer || dex.game;
|
||||||
|
// Which game bucket this save's catches belong to. The parser can't
|
||||||
|
// always tell (an RSE save could be Ruby or Emerald) — prefer the
|
||||||
|
// game the user currently has selected if it's one of the candidates.
|
||||||
|
const cands = dex.versionGroups || [];
|
||||||
|
const target = cands.includes(settings.get().versionGroup)
|
||||||
|
? settings.get().versionGroup
|
||||||
|
: cands[0];
|
||||||
const choice = await chooseDialog({
|
const choice = await chooseDialog({
|
||||||
title: `${dex.game}${dex.trainer ? ` · ${dex.trainer}` : ''}`,
|
title: `${dex.game}${dex.trainer ? ` · ${dex.trainer}` : ''}`,
|
||||||
body: `${dex.seen.length} seen · ${dex.caught.length} caught in this save.`,
|
body: `${dex.seen.length} seen · ${dex.caught.length} caught in this save.`,
|
||||||
choices: [
|
choices: [
|
||||||
{ key: 'merge', label: 'Add to my Pokédex' },
|
{ key: 'merge', label: `Add to ${prettify(target)}` },
|
||||||
{ key: 'replace', label: 'Replace mine with this', class: 'button--danger' },
|
{ key: 'replace', label: `Replace ${prettify(target)} with this`, class: 'button--danger' },
|
||||||
{ key: null, label: 'Cancel', class: 'button--ghost' },
|
{ key: null, label: 'Cancel', class: 'button--ghost' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (choice === 'merge') {
|
if (choice === 'merge') {
|
||||||
importFlags({ seen: dex.seen, caught: dex.caught });
|
importFlags({ seen: dex.seen, caught: dex.caught, game: target });
|
||||||
markPlayed(...(dex.versionGroups || []));
|
markPlayed(...cands);
|
||||||
saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen from ${who}.`;
|
saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen to ${prettify(target)}.`;
|
||||||
} else if (choice === 'replace') {
|
} else if (choice === 'replace') {
|
||||||
replaceFlags({ seen: dex.seen, caught: dex.caught });
|
replaceFlags({ seen: dex.seen, caught: dex.caught, game: target });
|
||||||
markPlayed(...(dex.versionGroups || []));
|
markPlayed(...cands);
|
||||||
saveNote.textContent = `Replaced tracking with ${who}'s Pokédex (favourites and notes kept).`;
|
saveNote.textContent = `Set ${prettify(target)}'s Pokédex from ${who}.`;
|
||||||
} else {
|
} else {
|
||||||
saveNote.textContent = '';
|
saveNote.textContent = '';
|
||||||
}
|
}
|
||||||
@ -178,7 +186,7 @@ export async function SettingsView() {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(await file.text());
|
const data = JSON.parse(await file.text());
|
||||||
if (data.settings) settings.replace(data.settings);
|
if (data.settings) settings.replace(data.settings);
|
||||||
if (data.selection) selection.replace(data.selection);
|
if (data.selection) selection.replace(normalizeSelection(data.selection));
|
||||||
if (data.team) team.replace(data.team);
|
if (data.team) team.replace(data.team);
|
||||||
if (data.formTracking) formTracking.replace(data.formTracking);
|
if (data.formTracking) formTracking.replace(data.formTracking);
|
||||||
if (data.shinyHunts) shinyHunts.replace(data.shinyHunts);
|
if (data.shinyHunts) shinyHunts.replace(data.shinyHunts);
|
||||||
@ -268,7 +276,7 @@ export async function SettingsView() {
|
|||||||
type: 'button',
|
type: 'button',
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) {
|
if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) {
|
||||||
selection.replace({ version: 2, pokemon: {} });
|
selection.replace({ version: 3, pokemon: {}, games: {} });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, 'Clear tracking data'),
|
}, 'Clear tracking data'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user