9a4dc80f11
The browser Back button now walks the app's own view stack:
- a small state object {tab, sub, parser, view, listtab, preset, suite,
script, item} is recorded on every view switch (navRecord/navCurrent/
navStateKey) via history.pushState - session-only, the URL never changes
(pushState throwing on file:// degrades to no history);
- popstate replays through the same switch functions under a _navApplying
guard (they re-render and re-translate): the seven tabs, every pill set
(SCP80 / Remote APDU + parser / Simulator / SCP81), the Cards list/editor
(preset id), the test-suite views (suite/script ids) and the Profiler
views/list tabs (profile/snapshot ids); unresolvable ids fall back to the
area's default view;
- the in-app Cancel/Back buttons (cardsCancelEdit, testScriptBack,
testBackToSuites, profilerBackToList, profilerSaveProfile) call
navReplace() so their entry is replaced instead of stacked, and the
initial entry is seeded with replaceState so the first Back returns to
the initial view.
The reported untranslated suite Run button was the tip of a larger gap: the
sweep found 21 t() keys without a Russian entry (Run/Suite/Move/Copy/Warning/
Text, the respond-result names, the event-data errors, the CAP tooltip, ...)
- all added, and html.test.js now asserts every t('...') literal and every
data-l10n/data-l10n-title attribute has a LANG_RU entry.
Tests: nav.test.js (the recording/replace hooks, the state-key
canonicalisation, the router core), the i18n completeness test, the
extracted-function stubs in the cards/phone/profiler tests.
842 frontend + 819 Python; sw cache simple-v409.
78 lines
2.1 KiB
JavaScript
78 lines
2.1 KiB
JavaScript
const CACHE = 'simple-v409';
|
|
const URLS = [
|
|
'index.html',
|
|
'help.html',
|
|
'help-ru.html',
|
|
'style.css',
|
|
'sim.png',
|
|
'favicon.svg',
|
|
'icon-192.png',
|
|
'icon-512.png',
|
|
'manifest.json',
|
|
'des-bundle.js',
|
|
'aes-bundle.js',
|
|
'sim.svg',
|
|
'sim_anim.svg',
|
|
'nosim.svg',
|
|
'uicc_files.json',
|
|
];
|
|
|
|
self.addEventListener('install', e => {
|
|
// Fetch the shell with `cache: 'reload'`: a plain addAll() would re-use
|
|
// HTTP-cached copies, so a new SW version could precache a stale
|
|
// style.css/index.html (the stale-style trap, v3.6.31).
|
|
e.waitUntil(
|
|
caches.open(CACHE).then(c =>
|
|
Promise.all(URLS.map(u => fetch(u, { cache: 'reload' }).then(r => {
|
|
if (r && r.ok) return c.put(u, r);
|
|
})))
|
|
)
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
|
|
self.addEventListener('activate', e => {
|
|
e.waitUntil(
|
|
caches.keys().then(keys =>
|
|
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
|
|
).then(() => clients.claim())
|
|
);
|
|
});
|
|
|
|
const OFFLINE_RESPONSE = new Response('Offline: page not cached', {
|
|
status: 503,
|
|
statusText: 'Offline',
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
});
|
|
|
|
self.addEventListener('fetch', e => {
|
|
if (!e.request.url.startsWith('http')) return;
|
|
const path = new URL(e.request.url).pathname;
|
|
if (path.startsWith('/api/')) return; // live data, never cache
|
|
if (e.request.method !== 'GET') return;
|
|
const isNavigate = e.request.mode === 'navigate';
|
|
const isSwScript = path.endsWith('/sw.js');
|
|
if (isNavigate) {
|
|
e.respondWith(
|
|
fetch(e.request).then(res => {
|
|
const clone = res.clone();
|
|
caches.open(CACHE).then(c => c.put(e.request, clone));
|
|
return res;
|
|
}).catch(() =>
|
|
caches.match(e.request)
|
|
.then(r => r || caches.match('index.html'))
|
|
.then(r => r || OFFLINE_RESPONSE)
|
|
)
|
|
);
|
|
} else if (isSwScript) {
|
|
e.respondWith(fetch(e.request).catch(() => OFFLINE_RESPONSE));
|
|
} else {
|
|
e.respondWith(
|
|
caches.match(e.request).then(r => r || fetch(e.request).then(res => {
|
|
const clone = res.clone();
|
|
caches.open(CACHE).then(c => c.put(e.request, clone));
|
|
return res;
|
|
}))
|
|
);
|
|
}
|
|
}); |