simplify, add pwa files during deploy

This commit is contained in:
Alexander Rose
2025-03-15 16:38:04 -07:00
parent 78aae8a2b4
commit 0f6fa5fe15
13 changed files with 224 additions and 215 deletions

15
data/pwa/README.md Normal file
View File

@@ -0,0 +1,15 @@
The files in this directory are used for deploying the Mol* Viewer as a PWA (Progressive Web App) at https://molstar.org/viewer/. They may serve as an example for creating your own PWA but wont work as-is. See `/script/deploy.js` for where these files are copied and how they are transformed during deployment.
## PWA features
- The Service Worker will cache static resources so the Viewer can be used without internet access. This works without installing, i.e., also in Firefox.
- Once installed, file types listed in the Manifest can be opened from, e.g., the Windows File Explorer.
## Notes for development
In Chrome you can see a list of installed PWAs at chrome://apps/. A right-click opens a menu with an option uninstall.
The Chrome Dev Tools have a section 'Application' to inspect and manage PWA aspects like the Manifest and Service Workers.

BIN
data/pwa/logo-144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,39 @@
{
"id": "https://molstar.org/viewer/",
"name": "Mol* Viewer",
"short_name": "Mol*",
"description": "Mol* Viewer: a modern web app for 3D visualization and analysis of large biomolecular structures.",
"start_url": "./index.html",
"theme_color": "#eeece7",
"background_color": "#eeece7",
"display": "standalone",
"icons": [
{
"src": "favicon.ico",
"sizes": "48x48"
},
{
"src": "logo-144.png",
"sizes": "144x144"
}
],
"file_handlers": [
{
"action": "./index.html",
"accept": {
"application/vnd.molstar": [".molx", ".molj"],
"text/plain": [
".mol", ".mol2", ".sdf", ".sd", ".pdb", ".ent", ".pdbqt", ".cif", ".mcif", ".mmcif", ".xyz", ".gro", ".lammpstrj",
".cub", ".cube", ".dx"
],
"application/octet-stream": [
".bcif",
".dxbin", ".ccp4", ".mrc", ".map", ".dsn6", ".brix"
]
}
}
],
"launch_handler": {
"client_mode": ["auto"]
}
}

44
data/pwa/pwa.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Andy Turner <agdturner@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
window.addEventListener('molstarViewerCreated', e => {
const viewer = e.detail.viewer;
// Handle incoming files
if ('launchQueue' in window) {
launchQueue.setConsumer((launchParams) => {
if (!launchParams.files.length) return;
const files = [];
for (const fileHandle of launchParams.files) {
files.push(fileHandle.getFile());
}
Promise.all(files).then((files) => {
viewer.loadFiles(files);
});
});
}
});
// Register Progressive Web App service worker.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./sw.js')
.then(function (registration) {
// Registration was successful
if (molstar.isDebugMode) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}
}, function (err) {
// registration failed :(
if (molstar.isDebugMode) {
console.error('ServiceWorker registration failed: ', err);
}
});
});
}

60
data/pwa/sw.js Normal file
View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Andy Turner <agdturner@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/** version from package.json, to be filled in during deployment */
const VERSION = '__MOLSTAR_VERSION__';
const CACHE_NAME = `molstar-viewer-${VERSION}`;
// The static resources that the app needs to function.
const APP_STATIC_RESOURCES = [
'favicon.ico',
'index.html',
'molstar.css',
'molstar.js',
'manifest.webmanifest',
'logo-144.png',
'pwa.js'
];
async function cacheStaticResources() {
const cache = await caches.open(CACHE_NAME);
await cache.addAll(APP_STATIC_RESOURCES);
await self.skipWaiting(); // Ensures the new service worker takes control immediately.
}
async function deleteOldCaches() {
const keys = await caches.keys();
await Promise.all(
keys.map((key) => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
}),
);
await self.clients.claim(); // Ensures the new service worker takes control immediately.
}
async function respondWithCacheFirst(request) {
// Try to match the request with the cache
const cachedResponse = await caches.match(request);
return cachedResponse || fetch(request);
}
self.addEventListener('install', (event) => {
// console.log(`Service Worker version ${VERSION} installed.`);
event.waitUntil(cacheStaticResources());
});
self.addEventListener('activate', (event) => {
// console.log(`Service Worker version ${VERSION} activated.`);
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
event.respondWith(respondWithCacheFirst(event.request));
});