mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-14 17:42:17 +00:00
93 lines
3.5 KiB
JavaScript
93 lines
3.5 KiB
JavaScript
const META_CACHE = 'livingdex-offline-meta-v1';
|
|
const META_URL = '/__offline/current';
|
|
|
|
function element(tag, className, text) {
|
|
const node = document.createElement(tag);
|
|
if (className) node.className = className;
|
|
if (text !== undefined) node.textContent = text;
|
|
return node;
|
|
}
|
|
|
|
function statusText(record) {
|
|
if (!record) return 'Not caught';
|
|
const values = [];
|
|
if (record.caught) values.push('Caught');
|
|
if (record.haveToEvolve) values.push('Needs evolution');
|
|
if (record.inHome) values.push('In HOME');
|
|
return values.join(' · ') || 'Not caught';
|
|
}
|
|
|
|
function renderSnapshot(snapshot) {
|
|
const content = document.querySelector('#offline-content');
|
|
for (const { pokedex, entries } of snapshot.pokedexes) {
|
|
const section = element('section', 'card bg-base-100 mb-6 shadow');
|
|
const body = element('div', 'card-body');
|
|
body.append(element('h2', 'card-title text-2xl', pokedex.name));
|
|
body.append(element('p', 'text-sm opacity-70', `${entries.length} entries`));
|
|
const grid = element(
|
|
'div',
|
|
'grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-6'
|
|
);
|
|
for (const { pokedexEntry: entry, catchRecord } of entries) {
|
|
const card = element('article', 'rounded border border-base-300 p-2');
|
|
const image = element('img', 'mx-auto h-20 w-20 object-contain');
|
|
image.alt = `${entry.pokemon}${entry.form ? ` — ${entry.form}` : ''}`;
|
|
image.src = entry.offlineSpriteUrl ?? '/placeholder-bulb.png';
|
|
image.addEventListener(
|
|
'error',
|
|
() => {
|
|
image.src = '/placeholder-bulb.png';
|
|
},
|
|
{ once: true }
|
|
);
|
|
card.append(image);
|
|
card.append(element('h3', 'font-semibold', `#${entry.pokedexNumber} ${entry.pokemon}`));
|
|
if (entry.form) card.append(element('p', 'text-xs opacity-70', entry.form));
|
|
card.append(element('p', 'text-sm', statusText(catchRecord)));
|
|
if (catchRecord?.personalNotes)
|
|
card.append(element('p', 'mt-1 whitespace-pre-wrap text-xs', catchRecord.personalNotes));
|
|
grid.append(card);
|
|
}
|
|
body.append(grid);
|
|
section.append(body);
|
|
content.append(section);
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
const status = document.querySelector('#offline-status');
|
|
try {
|
|
const meta = await (await (await caches.open(META_CACHE)).match(META_URL))?.json();
|
|
if (!meta?.userId) throw new Error('No collection has been synchronized on this device.');
|
|
if (!meta.dataCache) throw new Error('The saved collection metadata is incomplete.');
|
|
const dataCache = await caches.open(meta.dataCache);
|
|
const response = await dataCache.match(
|
|
`/__offline/snapshot/${encodeURIComponent(meta.userId)}`
|
|
);
|
|
if (!response)
|
|
throw new Error('The saved collection is incomplete. Reconnect and synchronize again.');
|
|
const snapshot = await response.json();
|
|
if (snapshot.version !== 1 || snapshot.userId !== meta.userId)
|
|
throw new Error('The saved collection is incompatible with this app version.');
|
|
status.textContent = `Saved ${new Date(snapshot.generatedAt).toLocaleString()}.`;
|
|
renderSnapshot(snapshot);
|
|
} catch (error) {
|
|
status.className = 'alert alert-warning';
|
|
status.textContent = error instanceof Error ? error.message : String(error);
|
|
}
|
|
}
|
|
|
|
void load();
|
|
|
|
navigator.serviceWorker?.addEventListener('message', (event) => {
|
|
if (event.data?.type !== 'OFFLINE_DATA_CLEARED') return;
|
|
const content = document.querySelector('#offline-content');
|
|
if (content) content.replaceChildren();
|
|
const status = document.querySelector('#offline-status');
|
|
if (status) {
|
|
status.className = 'alert alert-warning';
|
|
status.textContent =
|
|
'The saved collection was removed because the account changed or signed out.';
|
|
}
|
|
});
|