Skip to content

Commit 8f2c92a

Browse files
authored
Update navigation with fixed tab style (#53)
1 parent d2d3833 commit 8f2c92a

21 files changed

+603
-386
lines changed

generate-sw-manifest.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// generateManifest();
2+
import fs from 'node:fs/promises';
3+
import path from 'node:path';
4+
import crypto from 'node:crypto';
5+
6+
const DIST_DIR = 'dist';
7+
const MANIFEST_FILE = 'sw-manifest.js'; // Name of the file to create
8+
9+
async function getFileRevision(filePath) {
10+
try {
11+
const fileBuffer = await fs.readFile(filePath);
12+
const hashSum = crypto.createHash('md5');
13+
hashSum.update(fileBuffer);
14+
return hashSum.digest('hex');
15+
} catch (error) {
16+
console.error(`Error getting revision for ${filePath}:`, error);
17+
return null;
18+
}
19+
}
20+
21+
async function generateManifest() {
22+
const filesToCache = [];
23+
24+
async function traverseDir(dir) {
25+
const items = await fs.readdir(dir);
26+
for (const item of items) {
27+
const itemPath = path.join(dir, item);
28+
const stats = await fs.stat(itemPath);
29+
const relativePath = path.relative(DIST_DIR, itemPath).replace(/\\/g, '/'); // Ensure forward slashes for URLs
30+
31+
if (stats.isDirectory()) {
32+
await traverseDir(itemPath);
33+
} else {
34+
const revision = await getFileRevision(itemPath);
35+
if (revision) {
36+
filesToCache.push({ url: `/${relativePath}`, revision });
37+
} else {
38+
filesToCache.push({ url: `/${relativePath}`, revision: null }); // Or handle differently
39+
}
40+
}
41+
}
42+
}
43+
44+
await traverseDir(DIST_DIR);
45+
46+
const manifestContent = `const __WB_MANIFEST = ${JSON.stringify(filesToCache, null, 2)};`;
47+
48+
try {
49+
await fs.writeFile(path.join(DIST_DIR, MANIFEST_FILE), manifestContent, 'utf-8');
50+
console.log(`Manifest file "${MANIFEST_FILE}" generated in "${DIST_DIR}".`);
51+
} catch (error) {
52+
console.error('Error writing manifest file:', error);
53+
}
54+
}
55+
56+
generateManifest();

index.html

+11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@
105105
alert("Browser cache and local storage has been cleared.");
106106
});
107107
</script>
108+
<script>
109+
const navigationItems = document.querySelectorAll('.navigation-item');
110+
const currentPageUrl = window.location.pathname; // Get the current page path
111+
112+
navigationItems.forEach(item => {
113+
const linkButton = item.querySelector('sl-icon-button'); // Target the <sl-icon-button>
114+
if (linkButton && linkButton.getAttribute('href') === currentPageUrl) {
115+
item.classList.add('active');
116+
}
117+
});
118+
</script>
108119
</body>
109120

110121
</html>

package-lock.json

+6-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+7-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"build": "tsc && vite build",
1515
"start": "npm run dev",
1616
"start-remote": "vite --host",
17+
"postbuild": "node ./generate-sw-manifest.js",
1718
"clean-local": "npm cache clean --force && rm -rf ./node_modules/.cache"
1819
},
1920
"author": "",
@@ -26,10 +27,7 @@
2627
"localforage": "^1.10.0",
2728
"mana-font": "^1.18.0",
2829
"urlpattern-polyfill": "^10.0.0",
29-
"workbox-build": "^7.3.0",
30-
"workbox-core": "^7.3.0",
31-
"workbox-precaching": "^7.3.0",
32-
"workbox-routing": "7.3.0"
30+
"workbox-build": "^7.3.0"
3331
},
3432
"devDependencies": {
3533
"@types/node": "^22.13.9",
@@ -38,7 +36,11 @@
3836
"unplugin-fonts": "^1.3.1",
3937
"unplugin-icons": "^22.1.0",
4038
"vite": "^6.2.2",
41-
"vite-plugin-pwa": "^0.21.1"
39+
"vite-plugin-pwa": "^0.21.1",
40+
"workbox-core": "^7.3.0",
41+
"workbox-precaching": "^7.3.0",
42+
"workbox-routing": "^7.3.0",
43+
"workbox-strategies": "^7.3.0"
4244
},
4345
"prettier": {
4446
"tabWidth": 2,

public/sw.js

+144-110
Original file line numberDiff line numberDiff line change
@@ -2,120 +2,154 @@ importScripts(
22
'https://storage.googleapis.com/workbox-cdn/releases/7.3.0/workbox-sw.js'
33
);
44

5-
// This is your Service Worker, you can put any of your custom Service Worker
6-
// code in this file, above the `precacheAndRoute` line.
7-
8-
// When widget is installed/pinned, push initial state.
9-
self.addEventListener('widgetinstall', (event) => {
10-
event.waitUntil(updateWidget(event));
11-
});
12-
13-
// When widget is shown, update content to ensure it is up-to-date.
14-
self.addEventListener('widgetresume', (event) => {
15-
event.waitUntil(updateWidget(event));
16-
});
17-
18-
// When the user clicks an element with an associated Action.Execute,
19-
// handle according to the 'verb' in event.action.
20-
self.addEventListener('widgetclick', (event) => {
21-
if (event.action == "updateName") {
22-
event.waitUntil(updateName(event));
5+
if (workbox) {
6+
console.log('Workbox loaded successfully!');
7+
console.log('workbox.core:', workbox.core); // Add this line
8+
9+
workbox.core.initializeApp(); // Access initializeApp through the global workbox object
10+
11+
const { NetworkFirst, CacheFirst, StaleWhileRevalidate } = workbox.strategies;
12+
const { registerRoute } = workbox.routing;
13+
const { precacheAndRoute } = workbox.precaching;
14+
15+
// This is your Service Worker, you can put any of your custom Service Worker
16+
// code in this file, above the `precacheAndRoute` line.
17+
18+
// When widget is installed/pinned, push initial state.
19+
self.addEventListener('widgetinstall', (event) => {
20+
event.waitUntil(updateWidget(event));
21+
});
22+
23+
// When widget is shown, update content to ensure it is up-to-date.
24+
self.addEventListener('widgetresume', (event) => {
25+
event.waitUntil(updateWidget(event));
26+
});
27+
28+
// When the user clicks an element with an associated Action.Execute,
29+
// handle according to the 'verb' in event.action.
30+
self.addEventListener('widgetclick', (event) => {
31+
if (event.action == "updateName") {
32+
event.waitUntil(updateName(event));
33+
}
34+
});
35+
36+
// When the widget is uninstalled/unpinned, clean up any unnecessary
37+
// periodic sync or widget-related state.
38+
self.addEventListener('widgetuninstall', (event) => { });
39+
40+
const updateWidget = async (event) => {
41+
// The widget definition represents the fields specified in the manifest.
42+
const widgetDefinition = event.widget.definition;
43+
44+
// Fetch the template and data defined in the manifest to generate the payload.
45+
const payload = {
46+
template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),
47+
data: JSON.stringify(await (await fetch(widgetDefinition.data)).json()),
48+
};
49+
50+
// Push payload to widget.
51+
await self.widgets.updateByInstanceId(event.instanceId, payload);
2352
}
24-
});
2553

26-
// When the widget is uninstalled/unpinned, clean up any unnecessary
27-
// periodic sync or widget-related state.
28-
self.addEventListener('widgetuninstall', (event) => { });
54+
const updateName = async (event) => {
55+
const name = event.data.json().name;
2956

30-
const updateWidget = async (event) => {
31-
// The widget definition represents the fields specified in the manifest.
32-
const widgetDefinition = event.widget.definition;
57+
// The widget definition represents the fields specified in the manifest.
58+
const widgetDefinition = event.widget.definition;
3359

34-
// Fetch the template and data defined in the manifest to generate the payload.
35-
const payload = {
36-
template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),
37-
data: JSON.stringify(await (await fetch(widgetDefinition.data)).json()),
38-
};
60+
// Fetch the template and data defined in the manifest to generate the payload.
61+
const payload = {
62+
template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),
63+
data: JSON.stringify({ name }),
64+
};
3965

40-
// Push payload to widget.
41-
await self.widgets.updateByInstanceId(event.instanceId, payload);
42-
}
43-
44-
const updateName = async (event) => {
45-
const name = event.data.json().name;
46-
47-
// The widget definition represents the fields specified in the manifest.
48-
const widgetDefinition = event.widget.definition;
49-
50-
// Fetch the template and data defined in the manifest to generate the payload.
51-
const payload = {
52-
template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),
53-
data: JSON.stringify({ name }),
54-
};
55-
56-
// Push payload to widget.
57-
await self.widgets.updateByInstanceId(event.instanceId, payload);
58-
}
59-
60-
// Workbox Precaching
61-
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST || []);
62-
63-
// Cache data
64-
workbox.routing.registerRoute(
65-
({url}) => url.pathname.startsWith('/data'),
66-
new workbox.strategies.CacheFirst()
67-
);
68-
// Cache app images
69-
workbox.routing.registerRoute(
70-
({url}) => url.pathname.startsWith('/assets'),
71-
new workbox.strategies.CacheFirst()
72-
);
73-
74-
// Cache app CSS and JS files
75-
workbox.routing.registerRoute(
76-
({request}) => request.destination === 'script' ||
77-
request.destination === 'style' ||
78-
request.destination === 'module', // Cache dynamically imported modules
79-
new workbox.strategies.StaleWhileRevalidate()
80-
);
81-
82-
83-
// Navigation Routing
84-
workbox.routing.registerRoute(
85-
({ request }) => request.mode === 'navigate',
86-
workbox.strategies.networkFirst()
87-
);
88-
89-
self.addEventListener('push', function (event) {
90-
console.log('[Service Worker] Push Received.');
91-
if (Notification.permission === "granted") {
92-
const data = event.data.json();
93-
event.waitUntil(
94-
self.registration.showNotification(data.title, {
95-
body: data.body,
96-
icon: '/assets/icons/icon_192.png', // Replace with your icon
97-
tag: 'my-tag' // Optional: prevents multiple notifications with the same tag
98-
})
99-
);
100-
} else {
101-
console.log("Notification permission has been denied");
66+
// Push payload to widget.
67+
await self.widgets.updateByInstanceId(event.instanceId, payload);
10268
}
103-
});
104-
105-
self.addEventListener('notificationclick', (event) => {
106-
event.notification.close();
107-
var fullPath = self.location.origin + event.notification.data.path;
108-
clients.openWindow(fullPath);
109-
});
110-
111-
// Update notification events
112-
self.addEventListener('message', (event) => {
113-
if (event.data && event.data.type === 'SKIP_WAITING') {
114-
console.log("Skip waiting was called in the service worker.");
115-
self.skipWaiting();
116-
}
117-
});
11869

119-
self.addEventListener("install", (event) => {
120-
console.log("Service Worker install:", event)
121-
});
70+
// Workbox Precaching
71+
// precacheAndRoute(self.__WB_MANIFEST || []);
72+
73+
// Assuming your sl-icon icons are served from a specific path, e.g., '/assets/icons/' or a CDN
74+
registerRoute(
75+
({ url }) => url.pathname.startsWith('/assets/icons/') || url.host.includes('https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]'), // Adjust the URL pattern
76+
new CacheFirst({
77+
cacheName: 'sl-icon-cache',
78+
plugins: [
79+
new workbox.cacheableResponse.CacheableResponsePlugin({
80+
statuses: [0, 200], // Cache successful responses and opaque responses (for cross-origin)
81+
}),
82+
new workbox.expiration.ExpirationPlugin({
83+
maxAgeSeconds: 30 * 24 * 60 * 60, // Cache for 30 days (adjust as needed)
84+
maxEntries: 50, // Keep a maximum of 50 icon files (adjust as needed)
85+
}),
86+
],
87+
})
88+
);
89+
90+
// Cache data
91+
registerRoute(
92+
({ url }) => url.pathname.startsWith('/data'),
93+
new CacheFirst()
94+
);
95+
96+
// Cache app images
97+
registerRoute(
98+
({ url }) => url.pathname.startsWith('/assets'),
99+
new CacheFirst()
100+
);
101+
102+
// Cache app CSS and JS files
103+
registerRoute(
104+
({ request }) =>
105+
request.destination === 'script' ||
106+
request.destination === 'style' ||
107+
request.destination === 'module', // Cache dynamically imported modules
108+
new StaleWhileRevalidate()
109+
);
110+
111+
// Navigation Routing
112+
registerRoute(
113+
({ request }) => request.mode === 'navigate',
114+
new NetworkFirst()
115+
);
116+
117+
self.addEventListener('push', function (event) {
118+
console.log('[Service Worker] Push Received.');
119+
if (Notification.permission === "granted") {
120+
const data = event.data.json();
121+
event.waitUntil(
122+
self.registration.showNotification(data.title, {
123+
body: data.body,
124+
icon: '/assets/icons/icon_192.png', // Replace with your icon
125+
tag: 'my-tag' // Optional: prevents multiple notifications with the same tag
126+
})
127+
);
128+
} else {
129+
console.log("Notification permission has been denied");
130+
}
131+
});
132+
133+
self.addEventListener('notificationclick', (event) => {
134+
event.notification.close();
135+
var fullPath = self.location.origin + event.notification.data.path;
136+
clients.openWindow(fullPath);
137+
});
138+
139+
// Update notification events
140+
self.addEventListener('message', (event) => {
141+
if (event.data && event.data.type === 'SKIP_WAITING') {
142+
console.log("Skip waiting was called in the service worker.");
143+
self.skipWaiting();
144+
}
145+
});
146+
147+
self.addEventListener("install", (event) => {
148+
console.log("Service Worker install:", event)
149+
});
150+
151+
precacheAndRoute(self.__WB_MANIFEST || []); // Ensure your manifest also includes icon files if they are part of your build
152+
153+
} else {
154+
console.error('Workbox failed to load from CDN.');
155+
}

0 commit comments

Comments
 (0)